【小(xiǎo)編推薦】有(yǒu)關網頁渲染€  ∏,每個(gè)前端開(kāi)發者都(dōu)該知(zhī)道(₹☆≈dào)的(de)那(nà)點事(shì)

2015-06-13 &nbs♣↑∑‌p; |   發布者:梁國(g∞<"✔uó)芳   |&nb•& ≈sp;  查看(kàn):33∑←20次

IT新聞
 【編者按】其實,有(yǒu)關網頁渲染的(de)文(< ‌wén)章(zhāng)很(hěn)多(duō),但(dàn)是(sh​δ€ì)相(xiàng)關信息比較分(fēn)散,且論述并不(><>εbù)是(shì)很(hěn)完整。如(rú)果要(yào)想對(duì)這(¶λ×✔zhè)個(gè)主題有(yǒu)個(gè)大(d→€✘à)緻的(de)了(le)解,我們還(hái)得(de)學習(x₽±í)很(hěn)多(duō)知(zhī)識。因此,Web開↕ (kāi)發者Alexander Skutin 決定寫一(yī)≥​ ∑篇文(wén)章(zhāng)。他(tā)相(xiàng)信,這→β (zhè)篇文(wén)章(zhāng)不(bù ≠←)僅能(néng)幫助初學者,也(yě)能(néng)對(duì)那(n☆≈à)些(xiē)想要(yào)刷新知(zhī)識結構的(→→♣de)高(gāo)級前端開(kāi)發者有(yǒu)所裨益。

關于譯者:張超,OneAPM前端工(gōng)程師✘♦≤(shī),負責Browser Insight産品開(₽>±≥kāi)發。深度了(le)解浏覽器(qì)內(nèi)核,前端技(jì)術(φ$shù)“死忠粉”,具備多(duō)年(₹™₩₽nián)浏覽器(qì)性能(néng)優化(huà)經驗。


以下(xià)為(wèi)譯文(wén)

網頁渲染必須在很(hěn)早的(de)階段進行(xíng),可(€•kě)以早到(dào)頁面布局剛剛定型。因為(wèi)樣式和(hé)腳本®☆都(dōu)會(huì)對(duì)網頁渲染産生(γ← shēng)關鍵性的(de)影(yǐng)響。所以專業(yè)開(kγφβπāi)發者必須了(le)解一(yī)些(xiē)技(jì‍ )巧,從(cóng)而避免在實踐的(de)過程中遇到(dào)性能(™ε☆néng)問(wèn)題。

這(zhè)篇文(wén)章(zhāng)不(bù)會(huì)研究浏覽₽ ≥器(qì)內(nèi)部的(de)詳細機(jī)制••♣ (zhì),而是(shì)提出一(yī)些(xiē)通(tōng)用(yòn ∏≠g)的(de)規則。畢竟,不(bù)同浏覽器(q☆♥ì)引擎的(de)工(gōng)作(zuò)機(jī)制(zhì↕→)各不(bù)相(xiàng)同,這(zhè)無疑會¥>→(huì)讓開(kāi)發者對(duì)浏覽器(qì)特性的× ↕÷(de)研究變得(de)更加複雜(zá)。

浏覽器(qì)是(shì)如(rú)何完成網頁渲染?↕×<

首先,我們回顧一(yī)下(xià)網頁渲染時(shí),浏覽δ←≈器(qì)的(de)動作(zuò):

 

  1. 根據來(lái)自(zì)服務器(qì)端的(de)HTML代碼形₽<成文(wén)檔對(duì)象模型(DOM)。
  2. 加載并解析樣式,形成CSS對(duì)象模型。
  3. 在文(wén)檔對(duì)象模型和(hé)CSS對(duì)象ππ★模型之上(shàng),創建一(yī)棵由一(yī)組待生(shēng)成↓∑↔​渲染的(de)對(duì)象組成的(de)渲染樹(shù)(在Webσδβ¶kit中這(zhè)些(xiē)對(duì)象被稱為(wèi)渲染器(qì≠♠')或渲染對(duì)象,而在Gecko中稱之為(wèi)&ldq&φ♥‌uo;frame”。)渲染樹(shù)反映©↔←了(le)文(wén)檔對(duì)象模型的(d♠>e)結構,但(dàn)是(shì)不(bù)包含諸如(rú)λ≤£<head>标簽或含有(yǒu)`display:none`屬性的≠Ωεσ(de)不(bù)可(kě)見(jiàn)元素。在"≥←₹渲染樹(shù)中,每一(yī)段文(w✘✔λén)本字符串都(dōu)表現(xiàn)↑‌¶∑為(wèi)獨立的(de)渲染器(qì)。每一(yī)個(gè≈δ)渲染對(duì)象都(dōu)包含與之對(duì)應的(de)DOM對↓₽↑♣(duì)象,或者文(wén)本塊,還(hái)加上(shàn↓±g)計(jì)算(suàn)過的(de)樣式¶♦。換言之,渲染樹(shù)是(shì)一(yī)個(gè∑' )文(wén)檔對(duì)象模型的(de)直觀"¶β展示。
  4. 對(duì)渲染樹(shù)上(shàng)的(de)每個(gè)元素,計'φ₽★(jì)算(suàn)它的(de)坐(zuò)标,稱之為(wèi)布'≠局。浏覽器(qì)采用(yòng)一(yī)種流方®♣₽法,布局一(yī)個(gè)元素隻需通(tōng)過一( ±↑yī)次,但(dàn)是(shì)表格元↑✘π&素需要(yào)通(tōng)過多(duō)次。
  5. 最後,渲染樹(shù)上(shàng)的(de)元素最終展示在浏覽器(qì) δ裡(lǐ),這(zhè)一(yī)過程稱為(wèi)&ldq>δ"≤uo;painting”。

 

當用(yòng)戶與網頁交互,或者腳本程序改動修改網頁時(sπ'≈hí),前文(wén)提到(dào)的(de×®πσ)一(yī)些(xiē)操作(zuò)将會(γ©₹®huì)重複執行(xíng),因為(wèi)網頁<₽的(de)內(nèi)在結構已經發生(shēng)了(♦✘γεle)改變。

Repaint

當改變那(nà)些(xiē)不(bù)會(huì★•♦)影(yǐng)響元素在網頁中的(de)位置的(de)元素樣式時(shí) ↕,譬如(rú)background-coloΩ∑₩™r(背景色), border-color(邊框色), v★εisibility(可(kě)見(jiàn)性),浏覽器(qì)隻會(h↓σ€ uì)用(yòng)新的(de)樣式将元素重繪一(yī)次(這(zhè)↓π就(jiù)是(shì)重繪,或者說(shuō£≠£☆)重新構造樣式)。

Reflow

當改變影(yǐng)響到(dào)文(wén)本內(nèi)容或結構,或者元素φ✘•±位置時(shí),重排或者說(shuō)重新•Ω♦布局就(jiù)會(huì)發生(shēng)。這(zhè)些(xσσ♦iē)改變通(tōng)常由以下(xià)事(shì)件(jià∞≥∑n)觸發:

 

 

浏覽器(qì)如(rú)何優化(huà)渲染?

浏覽器(qì)盡可(kě)能(néng)将 repai♣£★€nt/reflow 限制(zhì)在被改©¥ε變元素的(de)區(qū)域內(nèi)。比如(rú),對(duì)于位置固"π↓≤定或絕對(duì)的(de)元素,其大(dà)小(xiǎo)改變隻影(yǐngαδ)響元素本身(shēn)及其子(zǐ)元★¥φ↕素,然而,靜(jìng)态定位元素的(de)大(dà)小(xiǎo)改變會(∑∞huì)觸發後續所有(yǒu)元素的(de)×&重流。

另一(yī)種優化(huà)技(jì)巧是(shε ì),在運行(xíng)幾段JavaScriφ→βpt代碼時(shí),浏覽器(qì)會(huì)緩φ ‌↓存這(zhè)些(xiē)改變,在代碼運↔•行(xíng)完畢後再将這(zhè)些(xiē)改變><經一(yī)次通(tōng)過加以應用(y©×≤òng)。舉個(gè)例子(zǐ),下(xià)面這(zhè)段代碼隻σ‌>會(huì)觸發一(yī)個(gè)reflow和₩ σ(hé)repaint:

 

var $body = $('body');
•"$body.css('padding', '1px'); // reflo↔★☆≥w, repaint
$body.css('color', 'red');​↕£ // repaint
$body.css(α≥'margin', '2px'); //€σ®× reflow, repaint
// only 1 reflo♠✔​πw and repaint will actually φ™happen

 

然而,如(rú)前所述,改變元素的(de)屬性會(huì)觸發強制(zhì)性σ±的(de)重排。如(rú)果我們在上(shàng)面的(de)​α£代碼塊中加入一(yī)行(xíng)代碼☆ ‌,用(yòng)來(lái)訪問(wèn)元素的(de)屬性∏₹,就(jiù)會(huì)發生(shēng)這(zhè)種現(xiàn)象'→。

 

var $body = $('body');
$body.css(↔β'padding', '1px');
$body.css(±∞​'padding'); // reading a p≈βroperty, a forced reflow
$bodyβ∞₩.css('color', 'red');±&↑
$body.css('margin', '2px');

 

其結果就(jiù)是(shì),重排發生(s× φhēng)了(le)兩次。因此,你(nǐ)應該把訪問(wèn)元素屬性♦εα的(de)操作(zuò)都(dōu)組織在一(yī)起,從(≥ ♥→cóng)而優化(huà)網頁性能(néng)。([你(nǐ)可(kě)☆¶&以在JSBin查到(dào)更為(wèi)詳細的(de)例子(♥¶α↕zǐ)](http://jsbin.com/duhah/2/←β₹edit?html,css,js,output))

有(yǒu)時(shí),你(nǐ)必須觸發一(yī)個✘♥$(gè)強制(zhì)性重排。比如(rú),我們必須将同λ≤♠樣的(de)屬性(比如(rú)左邊距)兩次賦值給同一(yī)個(gè)元素λ< ®。起初,它應該設置為(wèi)100px¥βπ,且不(bù)帶動效。接著(zhe),它必須通(t≈÷©ōng)過過渡(transition)動效改σ₽變為(wèi)50px。你(nǐ)現(xiàn§ ®λ)在可(kě)以在[JSbin](http≠ ₩://jsbin.com/duhah/2/edit?h→©©tml,css,js,output)上(shàng)學÷₽∑♥習(xí)這(zhè)個(gè)例子(zǐ),不(bù★§Ω₹)過我會(huì)在這(zhè)兒(ér)更詳細地(dì)介‍₽紹它。

首先,我們創建一(yī)個(gè)帶過渡效果☆γ∞★的(de)CSS類:

 

.has-transition {
   -webk®€it-transition: margin-left 1☆₽£βs ease-out;
      -moz-trans₽φ ition: margin-left 1s ease-out;
€♥☆
        -o-transition: margin-le​♣ft 1s ease-out;
           transition:₩φ• margin-left 1s ease-out;
ε>₹ }

 

然後繼續執行(xíng):

 

// our element that has a &♥§≤σquot;has-transition" class by ​←$default
var $targetElem ♠≈= $('#targetElemId');

/✔¶/ remove the transition cla₩©ss
$targetElem.removeCl★αass('has-transition');

// ch↓¥ange the property expecting✔→ the transition to b♦γ™•e off, as the class is not there
// Ω anymore
$targetElemφ∞>.css('margin-left', 100);

// put th©♦₽♣e transition class back
$targetElem§←™ .addClass('has-transition');
 ♣¶
// change the property
$≈→targetElem.css('margin-le<•ft', 50);

 

然而,這(zhè)個(gè)執行(xíng)無法奏效。所有(yǒu)改變都☆€Ω(dōu)被緩存,隻在代碼塊末尾加以執行α₹​™(xíng)。我們需要(yào)的(de)是(shì)強制¶♥Ω(zhì)性的(de)重排,我們可(kě)以♥‍通(tōng)過以下(xià)更改加以實現(xiàn):

 

// remove the transition class
$(this ≤"±).removeClass('has-transition');
"σ•α

// change the property
$(this).css( ‍​'margin-left', 100);

☆Ωα
// trigger a forced↓€↑₽ reflow, so that changes in a class/pro ♠perty get applied immediately
$(t÷♦©¥his)[0].offsetHeight; // an example, ot&∞ her properties would work♣•, too

// put the transition cΩδσlass back
$(this).addClass('has-t™ ✘ransition');

// change γλ‍φthe property
$(this ♣™).css('margin-left', 50);

 

現(xiàn)在代碼如(rú)預期那(nà)樣 &★‌執行(xíng)了(le)。

有(yǒu)關性能(néng)優化(huà)的(de)實際建議(yì↕§<)

總結現(xiàn)有(yǒu)的(de)資料₹ ,我提出以下(xià)建議(yì):

 

 

 

  1. 識别器(qì):#id
  2. 類:.class
  3. 标簽:div
  4. 相(xiàng)鄰兄弟(dì)選擇器(q↔'ì):a + i
  5. 父類選擇器(qì):ul> li
  6. 通(tōng)用(yòng)選擇器(qì):*
  7. 屬性選擇:input[type="text"]≥ £
  8. 僞類和(hé)僞元素:a:hover

 

你(nǐ)應該記住,浏覽器(qì)在處理(lǐ)選擇器(q±§ ì)時(shí)依照(zhào)從(cóng)右到(≈£≈dào)左的(de)原則,因此最右端的(d₹¶e)選擇器(qì)應該是(shì)最快(kuài)的♠∞(de):#id或則.class:

 

div * {...} // bad
.list li {.₽ "..} // bad
.list-item ''{...} // good
#list .list-item {...}  Ω★// good