瀏覽器渲染原理與過(guò)程

瀏覽器如何渲染網(wǎng)頁(yè)

要了解瀏覽器渲染頁(yè)面的過(guò)程,首先得知道一個(gè)名詞——關(guān)鍵渲染路徑烘跺。關(guān)鍵渲染路徑是指瀏覽器從最初接收請(qǐng)求來(lái)的HTML萎坷、CSS、javascript等資源成艘,然后解析拇砰、構(gòu)建樹(shù)、渲染布局狰腌、繪制除破,最后呈現(xiàn)給用戶能看到的界面這整個(gè)過(guò)程。

用戶看到頁(yè)面實(shí)際上可以分為兩個(gè)階段:頁(yè)面內(nèi)容加載完成和頁(yè)面資源加載完成琼腔,分別對(duì)應(yīng)于DOMContentLoadedLoad瑰枫。

  • DOMContentLoaded事件觸發(fā)時(shí),僅當(dāng)DOM加載完成,不包括樣式表光坝,圖片等
  • load事件觸發(fā)時(shí)尸诽,頁(yè)面上所有的DOM,樣式表盯另,腳本性含,圖片都已加載完成

瀏覽器渲染的過(guò)程主要包括以下五步:

  1. 瀏覽器將獲取的HTML文檔解析成DOM樹(shù)。
  2. 處理CSS標(biāo)記鸳惯,構(gòu)成層疊樣式表模型CSSOM(CSS Object Model)商蕴。
  3. 將DOM和CSSOM合并為渲染樹(shù)(rendering tree),代表一系列將被渲染的對(duì)象芝发。
  4. 渲染樹(shù)的每個(gè)元素包含的內(nèi)容都是計(jì)算過(guò)的绪商,它被稱之為布局layout。瀏覽器使用一種流式處理的方法辅鲸,只需要一次繪制操作就可以布局所有的元素格郁。
  5. 將渲染樹(shù)的各個(gè)節(jié)點(diǎn)繪制到屏幕上,這一步被稱為繪制painting独悴。

需要注意的是例书,以上五個(gè)步驟并不一定一次性順序完成,比如DOM或CSSOM被修改時(shí)刻炒,亦或是哪個(gè)過(guò)程會(huì)重復(fù)執(zhí)行决采,這樣才能計(jì)算出哪些像素需要在屏幕上進(jìn)行重新渲染。而在實(shí)際情況中落蝙,JavaScript和CSS的某些操作往往會(huì)多次修改DOM或者CSSOM织狐。

webkit渲染引擎流程

瀏覽器渲染網(wǎng)頁(yè)的具體流程

構(gòu)建DOM樹(shù)

當(dāng)瀏覽器接收到服務(wù)器響應(yīng)來(lái)的HTML文檔后暂幼,會(huì)遍歷文檔節(jié)點(diǎn)筏勒,生成DOM樹(shù)。
需要注意以下幾點(diǎn):

  • DOM樹(shù)在構(gòu)建的過(guò)程中可能會(huì)被CSS和JS的加載而執(zhí)行阻塞
  • display:none的元素也會(huì)在DOM樹(shù)中
  • 注釋也會(huì)在DOM樹(shù)中
  • script標(biāo)簽會(huì)在DOM樹(shù)中

無(wú)論是DOM還是CSSOM旺嬉,都是要經(jīng)過(guò)Bytes→characters→tokens→nodes→object model這個(gè)過(guò)程管行。

當(dāng)前節(jié)點(diǎn)的所有子節(jié)點(diǎn)都構(gòu)建好后才會(huì)去構(gòu)建當(dāng)前節(jié)點(diǎn)的下一個(gè)兄弟節(jié)點(diǎn)。

構(gòu)建CSSOM規(guī)則樹(shù)

瀏覽器解析CSS文件并生成CSSOM邪媳,每個(gè)CSS文件都被分析成一個(gè)StyleSheet對(duì)象捐顷,每個(gè)對(duì)象都包含CSS規(guī)則。CSS規(guī)則對(duì)象包含對(duì)應(yīng)于CSS語(yǔ)法的選擇器和聲明對(duì)象以及其他對(duì)象雨效。
在這個(gè)過(guò)程需要注意的是:

  • CSS解析可以與DOM解析同時(shí)進(jìn)行迅涮。
  • CSS解析與script的執(zhí)行互斥 。
  • 在Webkit內(nèi)核中進(jìn)行了script執(zhí)行優(yōu)化徽龟,只有在JS訪問(wèn)CSS時(shí)才會(huì)發(fā)生互斥叮姑。

構(gòu)建渲染樹(shù)(Render Tree)

通過(guò)DOM樹(shù)和CSS規(guī)則樹(shù),瀏覽器就可以通過(guò)它兩構(gòu)建渲染樹(shù)了。瀏覽器會(huì)先從DOM樹(shù)的根節(jié)點(diǎn)開(kāi)始遍歷每個(gè)可見(jiàn)節(jié)點(diǎn)传透,然后對(duì)每個(gè)可見(jiàn)節(jié)點(diǎn)找到適配的CSS樣式規(guī)則并應(yīng)用耘沼。
有以下幾點(diǎn)需要注意:

  • Render Tree和DOM Tree不完全對(duì)應(yīng)
  • display: none的元素不在Render Tree中
  • visibility: hidden的元素在Render Tree中

渲染樹(shù)生成后,還是沒(méi)有辦法渲染到屏幕上朱盐,渲染到屏幕需要得到各個(gè)節(jié)點(diǎn)的位置信息群嗤,這就需要布局(Layout)的處理了。

渲染樹(shù)布局(layout of the render tree)

布局階段會(huì)從渲染樹(shù)的根節(jié)點(diǎn)開(kāi)始遍歷兵琳,由于渲染樹(shù)的每個(gè)節(jié)點(diǎn)都是一個(gè)Render Object對(duì)象狂秘,包含寬高,位置闰围,背景色等樣式信息赃绊。所以瀏覽器就可以通過(guò)這些樣式信息來(lái)確定每個(gè)節(jié)點(diǎn)對(duì)象在頁(yè)面上的確切大小和位置,布局階段的輸出就是我們常說(shuō)的盒子模型羡榴,它會(huì)精確地捕獲每個(gè)元素在屏幕內(nèi)的確切位置與大小碧查。需要注意的是:

  • float元素,absoulte元素校仑,fixed元素會(huì)發(fā)生位置偏移忠售。
  • 我們常說(shuō)的脫離文檔流,其實(shí)就是脫離Render Tree迄沫。

渲染樹(shù)繪制(Painting the render tree)

在繪制階段稻扬,瀏覽器會(huì)遍歷渲染樹(shù),調(diào)用渲染器的paint()方法在屏幕上顯示其內(nèi)容羊瘩。渲染樹(shù)的繪制工作是由瀏覽器的UI后端組件完成的泰佳。

瀏覽器渲染網(wǎng)頁(yè)的那些事兒

瀏覽器主要組件結(jié)構(gòu)

瀏覽器主要組件

渲染引擎主要有兩個(gè):webkit和Gecko
Firefox使用Geoko,Mozilla自主研發(fā)的渲染引擎尘吗。Safari和Chrome都使用webkit逝她。Webkit是一款開(kāi)源渲染引擎,它本來(lái)是為linux平臺(tái)研發(fā)的睬捶,后來(lái)由Apple移植到Mac及Windows上黔宛。
雖然主流瀏覽器渲染過(guò)程叫法有區(qū)別,但是主要流程還是相同的擒贸。

渲染阻塞

JS可以操作DOM來(lái)修改DOM結(jié)構(gòu)臀晃,可以操作CSSOM來(lái)修改節(jié)點(diǎn)樣式,這就導(dǎo)致了瀏覽器在遇到<script>標(biāo)簽時(shí)介劫,DOM構(gòu)建將暫停徽惋,直至腳本完成執(zhí)行,然后繼續(xù)構(gòu)建DOM座韵。如果腳本是外部的险绘,會(huì)等待腳本下載完畢,再繼續(xù)解析文檔。現(xiàn)在可以在script標(biāo)簽上增加屬性defer或者async隆圆。腳本解析會(huì)將腳本中改變DOM和CSS的地方分別解析出來(lái)漱挚,追加到DOM樹(shù)和CSSOM規(guī)則樹(shù)上。

每次去執(zhí)行JavaScript腳本都會(huì)嚴(yán)重地阻塞DOM樹(shù)的構(gòu)建渺氧,如果JavaScript腳本還操作了CSSOM旨涝,而正好這個(gè)CSSOM還沒(méi)有下載和構(gòu)建,瀏覽器甚至?xí)舆t腳本執(zhí)行和構(gòu)建DOM侣背,直至完成其CSSOM的下載和構(gòu)建白华。所以,script標(biāo)簽的位置很重要贩耐。

JS阻塞了構(gòu)建DOM樹(shù)弧腥,也阻塞了其后的構(gòu)建CSSOM規(guī)則樹(shù),整個(gè)解析進(jìn)程必須等待JS的執(zhí)行完成才能夠繼續(xù)潮太,這就是所謂的JS阻塞頁(yè)面管搪。

由于CSSOM負(fù)責(zé)存儲(chǔ)渲染信息,瀏覽器就必須保證在合成渲染樹(shù)之前铡买,CSSOM是完備的更鲁,這種完備是指所有的CSS(內(nèi)聯(lián)、內(nèi)部和外部)都已經(jīng)下載完奇钞,并解析完澡为,只有CSSOM和DOM的解析完全結(jié)束,瀏覽器才會(huì)進(jìn)入下一步的渲染景埃,這就是CSS阻塞渲染媒至。

CSS阻塞渲染意味著,在CSSOM完備前谷徙,頁(yè)面將一直處理白屏狀態(tài)拒啰,這就是為什么樣式放在head中,僅僅是為了更快的解析CSS蒂胞,保證更快的首次渲染图呢。

需要注意的是条篷,即便你沒(méi)有給頁(yè)面任何的樣式聲明骗随,CSSOM依然會(huì)生成,默認(rèn)生成的CSSOM自帶瀏覽器默認(rèn)樣式赴叹。

當(dāng)解析HTML的時(shí)候鸿染,會(huì)把新來(lái)的元素插入DOM樹(shù)里面,同時(shí)去查找CSS乞巧,然后把對(duì)應(yīng)的樣式規(guī)則應(yīng)用到元素上涨椒,查找樣式表是按照從右到左的順序去匹配的。

例如:div p {font-size: 16px},會(huì)先尋找所有p標(biāo)簽并判斷它的父標(biāo)簽是否為div之后才會(huì)決定要不要采用這個(gè)樣式進(jìn)行渲染)蚕冬。
所以免猾,我們平時(shí)寫CSS時(shí),盡量用idclass囤热,千萬(wàn)不要過(guò)渡層疊猎提。

回流和重繪(reflow和repaint)

我們都知道HTML默認(rèn)是流式布局的,但CSS和JS會(huì)打破這種布局旁蔼,改變DOM的外觀樣式以及大小和位置锨苏。因此我們就需要知道兩個(gè)概念:replaintreflow

reflow(回流)

當(dāng)瀏覽器發(fā)現(xiàn)布局發(fā)生了變化棺聊,這個(gè)時(shí)候就需要倒回去重新渲染伞租,這個(gè)回退的過(guò)程叫reflowreflow會(huì)從html這個(gè)root frame開(kāi)始遞歸往下限佩,依次計(jì)算所有的結(jié)點(diǎn)幾何尺寸和位置葵诈,以確認(rèn)是渲染樹(shù)的一部分發(fā)生變化還是整個(gè)渲染樹(shù)。reflow幾乎是無(wú)法避免的祟同,因?yàn)橹灰脩暨M(jìn)行交互操作驯击,就勢(shì)必會(huì)發(fā)生頁(yè)面的一部分的重新渲染,且通常我們也無(wú)法預(yù)估瀏覽器到底會(huì)reflow哪一部分的代碼耐亏,因?yàn)樗麄儠?huì)相互影響徊都。

repaint(重繪)

repaint則是當(dāng)我們改變某個(gè)元素的背景色、文字顏色广辰、邊框顏色等等不影響它周圍或內(nèi)部布局的屬性時(shí)暇矫,屏幕的一部分要重畫,但是元素的幾何尺寸和位置沒(méi)有發(fā)生改變择吊。

需要注意的是李根,display:none會(huì)觸發(fā)reflow,而visibility: hidden屬性則并不算是不可見(jiàn)屬性几睛,它的語(yǔ)義是隱藏元素房轿,但元素仍然占據(jù)著布局空間,它會(huì)被渲染成一個(gè)空框所森。所以visibility:hidden只會(huì)觸發(fā)repaint囱持,因?yàn)闆](méi)有發(fā)生位置變化。

另外有些情況下焕济,比如修改了元素的樣式纷妆,瀏覽器并不會(huì)立刻reflowrepaint一次,而是會(huì)把這樣的操作積攢一批晴弃,然后做一次reflow掩幢,這又叫異步reflow或增量異步reflow逊拍。但是在有些情況下,比如resize窗口际邻,改變了頁(yè)面默認(rèn)的字體等芯丧。對(duì)于這些操作,瀏覽器會(huì)馬上進(jìn)行reflow世曾。

引起reflow

現(xiàn)代瀏覽器會(huì)對(duì)回流做優(yōu)化注整,它會(huì)等到足夠數(shù)量的變化發(fā)生,再做一次批處理回流度硝。

  • 頁(yè)面第一次渲染(初始化)
  • DOM樹(shù)變化(如:增刪節(jié)點(diǎn))
  • Render樹(shù)變化(如:padding改變)
  • 瀏覽器窗口resize
  • 獲取元素的某些屬性

瀏覽器為了獲得正確的值也會(huì)提前觸發(fā)回流肿轨,這樣就使得瀏覽器的優(yōu)化失效了,這些屬性包括offsetLeft蕊程、offsetTop椒袍、offsetWidth、offsetHeight藻茂、 scrollTop/Left/Width/Height驹暑、clientTop/Left/Width/Height、調(diào)用了getComputedStyle()辨赐。

引起repaint

reflow回流必定引起repaint重繪优俘,重繪可以單獨(dú)觸發(fā)。
背景色掀序、顏色帆焕、字體改變(注意:字體大小發(fā)生變化時(shí),會(huì)觸發(fā)回流)

減少reflow不恭、repaint觸發(fā)次數(shù)
  • transform做形變和位移可以減少reflow
  • 避免逐個(gè)修改節(jié)點(diǎn)樣式叶雹,盡量一次性修改
  • 使用DocumentFragment將需要多次修改的DOM元素緩存,最后一次性append到真實(shí)DOM中渲染
  • 可以將需要多次修改的DOM元素設(shè)置display:none换吧,操作完再顯示折晦。(因?yàn)殡[藏元素不在render樹(shù)內(nèi),因此修改隱藏元素不會(huì)觸發(fā)回流重繪)
  • 避免多次讀取某些屬性
  • 通過(guò)絕對(duì)位移將復(fù)雜的節(jié)點(diǎn)元素脫離文檔流沾瓦,形成新的Render Layer满着,降低回流成本

幾條關(guān)于優(yōu)化渲染效率的建議

結(jié)合上文有以下幾點(diǎn)可以優(yōu)化渲染效率。

  • 合法地去書寫HTML和CSS 贯莺,且不要忘了文檔編碼類型风喇。
  • 樣式文件應(yīng)當(dāng)在head標(biāo)簽中,而腳本文件在body結(jié)束前乖篷,這樣可以防止阻塞的方式响驴。
  • 簡(jiǎn)化并優(yōu)化CSS選擇器透且,盡量將嵌套層減少到最小撕蔼。
  • DOM 的多個(gè)讀操作(或多個(gè)寫操作)豁鲤,應(yīng)該放在一起。不要兩個(gè)讀操作之間鲸沮,加入一個(gè)寫操作琳骡。
  • 如果某個(gè)樣式是通過(guò)重排得到的,那么最好緩存結(jié)果讼溺。避免下一次用到的時(shí)候楣号,瀏覽器又要重排。
  • 不要一條條地改變樣式怒坯,而要通過(guò)改變class炫狱,或者csstext屬性,一次性地改變樣式剔猿。
  • 盡量用transform來(lái)做形變和位移
  • 盡量使用離線DOM视译,而不是真實(shí)的網(wǎng)頁(yè)DOM,來(lái)改變?cè)貥邮焦榫础1热缈岷僮?code>Document Fragment對(duì)象,完成后再把這個(gè)對(duì)象加入DOM汪茧。再比如椅亚,使用cloneNode()方法,在克隆的節(jié)點(diǎn)上進(jìn)行操作舱污,然后再用克隆的節(jié)點(diǎn)替換原始節(jié)點(diǎn)呀舔。
  • 先將元素設(shè)為display: none(需要1次重排和重繪),然后對(duì)這個(gè)節(jié)點(diǎn)進(jìn)行100次操作扩灯,最后再恢復(fù)顯示(需要1次重排和重繪)别威。這樣一來(lái),你就用兩次重新渲染驴剔,取代了可能高達(dá)100次的重新渲染省古。
  • position屬性為absolutefixed的元素,重排的開(kāi)銷會(huì)比較小丧失,因?yàn)椴挥每紤]它對(duì)其他元素的影響豺妓。
  • 只在必要的時(shí)候,才將元素的display屬性為可見(jiàn)布讹,因?yàn)椴豢梢?jiàn)的元素不影響重排和重繪琳拭。另外,visibility : hidden的元素只對(duì)重繪有影響描验,不影響重排白嘁。
  • 使用window.requestAnimationFrame()window.requestIdleCallback()這兩個(gè)方法調(diào)節(jié)重新渲染膘流。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末絮缅,一起剝皮案震驚了整個(gè)濱河市鲁沥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌耕魄,老刑警劉巖画恰,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異吸奴,居然都是意外死亡允扇,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門则奥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)考润,“玉大人,你說(shuō)我怎么就攤上這事读处《罨” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵档泽,是天一觀的道長(zhǎng)俊戳。 經(jīng)常有香客問(wèn)我,道長(zhǎng)馆匿,這世上最難降的妖魔是什么抑胎? 我笑而不...
    開(kāi)封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮渐北,結(jié)果婚禮上阿逃,老公的妹妹穿的比我還像新娘。我一直安慰自己赃蛛,他們只是感情好恃锉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著呕臂,像睡著了一般破托。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上歧蒋,一...
    開(kāi)封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天土砂,我揣著相機(jī)與錄音,去河邊找鬼谜洽。 笑死萝映,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的阐虚。 我是一名探鬼主播序臂,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼实束!你這毒婦竟也來(lái)了奥秆?” 一聲冷哼從身側(cè)響起逊彭,我...
    開(kāi)封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吭练,沒(méi)想到半個(gè)月后诫龙,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體析显,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鲫咽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谷异。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片分尸。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖歹嘹,靈堂內(nèi)的尸體忽然破棺而出箩绍,到底是詐尸還是另有隱情,我是刑警寧澤尺上,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布材蛛,位于F島的核電站,受9級(jí)特大地震影響怎抛,放射性物質(zhì)發(fā)生泄漏卑吭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一马绝、第九天 我趴在偏房一處隱蔽的房頂上張望豆赏。 院中可真熱鬧,春花似錦富稻、人聲如沸掷邦。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)抚岗。三九已至,卻和暖如春哪怔,著一層夾襖步出監(jiān)牢的瞬間苟跪,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工蔓涧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留件已,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓元暴,卻偏偏與公主長(zhǎng)得像篷扩,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子茉盏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 原文地址:http://srtian96.gitee.io/blog/2018/06/01/%E6%B5%8F%E...
    Srtian閱讀 4,887評(píng)論 0 11
  • 發(fā)送 & 接收信息 數(shù)據(jù)是以“數(shù)據(jù)包”的形式通過(guò)互聯(lián)網(wǎng)發(fā)送鉴未,而數(shù)據(jù)包以字節(jié)為單位枢冤。當(dāng)你編寫一些 HTML、CSS ...
    mongofeng閱讀 917評(píng)論 0 0
  • 發(fā)送 & 接收信息 數(shù)據(jù)是以“數(shù)據(jù)包”的形式通過(guò)互聯(lián)網(wǎng)發(fā)送铜秆,而數(shù)據(jù)包以字節(jié)為單位淹真。當(dāng)你編寫一些 HTML、CSS ...
    IT界中小PQ閱讀 405評(píng)論 0 0
  • 大家都知道萬(wàn)維網(wǎng)的應(yīng)用層使用了HTTP協(xié)議连茧,并且用瀏覽器作為入口訪問(wèn)網(wǎng)絡(luò)上的資源核蘸。用戶在使用瀏覽器訪問(wèn)一個(gè)網(wǎng)站時(shí)需...
    SylvanasSun閱讀 2,149評(píng)論 1 12
  • 我不知道該怎樣再生活下去 不會(huì)再情緒崩潰大哭 卻也沉靜的面對(duì)腦子里蹦出的自殺的念頭 平靜的時(shí)候開(kāi)始擔(dān)心如果哪一天,...
    胖拖閱讀 154評(píng)論 0 0