国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

天生就慢的DOM如何優(yōu)化?

ZoomQuiet / 2426人閱讀

摘要:當(dāng)遍歷一個(gè)集合時(shí),首要優(yōu)化原則是把集合存儲在局部變量中,并把緩存在循環(huán)外部,然后使用局部變量訪問這些需要多次訪問的元素。

友情提醒:這篇我都覺得有點(diǎn)長...可能會占用你10+分鐘,沒有這么多時(shí)間的你可以直接去文末看小結(jié)。

用腳本進(jìn)行DOM操作的代價(jià)是很昂貴的,它是富web應(yīng)用中最常見的性能瓶頸。主要有以下三種問題:

訪問和修改DOM元素

修改DOM元素的樣式導(dǎo)致repaint和reflow

通過DOM事件處理與用戶進(jìn)行交互

瀏覽器中的DOM

DOM是(Document Object Model)一個(gè)與語言無關(guān)的、用來操作XML和HTML文檔的應(yīng)用程序接口(Application Program Interface)。 盡管DOM與語言無關(guān),但是在瀏覽器中的接口卻是用JavaScript來實(shí)現(xiàn)的。

一個(gè)前端小知識

瀏覽器通常會把js和DOM分開來分別獨(dú)立實(shí)現(xiàn)。
舉個(gè)栗子冷知識,在IE中,js的實(shí)現(xiàn)名為JScript,位于jscript.dll文件中;DOM的實(shí)現(xiàn)則存在另一個(gè)庫中,名為mshtml.dll(Trident)。
Chrome中的DOM實(shí)現(xiàn)為webkit中的webCore,但js引擎是Google自己研發(fā)的V8。
Firefox中的js引擎是SpiderMonkey,渲染引擎(DOM)則是Gecko。

DOM,天生就慢

前面的小知識中說過,瀏覽器把實(shí)現(xiàn)頁面渲染的部分和解析js的部分分開來實(shí)現(xiàn),既然是分開的,一旦兩者需要產(chǎn)生連接,就要付出代價(jià)。
兩個(gè)例子:

小明和小紅是兩個(gè)不同學(xué)校的學(xué)生,兩個(gè)人家里經(jīng)濟(jì)條件都不太好,買不起手機(jī)(好尷尬的設(shè)定Orz...),所以只能通過寫信來互相交流,這樣的過程肯定比他倆面對面交談時(shí)所需要花費(fèi)的代價(jià)大(額外的事件、寫信的成本等)。

官方例子:把DOM和js(ECMAScript)各自想象為一座島嶼,它們之間用收費(fèi)橋進(jìn)行連接。ECMAScript每次訪問DOM,都要途徑這座橋,并交納“過橋費(fèi)”。訪問DOM的次數(shù)越多,費(fèi)用也就越高。

因此,推薦的做法是:盡可能的減少過橋的次數(shù),努力待在ECMAScript島上

DOM的訪問與修改

前面說到訪問DOM需要交納“過橋費(fèi)”,而修改DOM元素則代價(jià)更為昂貴,因?yàn)樗鼤?dǎo)致瀏覽器重新計(jì)算頁面的幾何變化。
來看一段代碼:

function innerHTMLLoop(){  
    for (var count = 0; count < 15000; count++){  
        document.getElementById("text").innerHTML += "dom";  
    }  
}

這段代碼,每次循環(huán)會訪問兩次特定的元素:第一次讀取這個(gè)元素的innerHTML屬性,第二次重寫它。
看清楚了這一點(diǎn),不難得到一個(gè)效率更高的版本:

function innerHTMLLoop2(){  
    var content = "";  
    for (var count = 0; count < 15000; count++){  
        content += "dom";  
    }  
    document.getElementById("text").innerHTML += content;  
}

用一個(gè)局部變量包層每次更新后的內(nèi)容,等待循環(huán)結(jié)束后,一次性的寫入頁面(盡可能的把更多的工作交給js的部分來做)。
根據(jù)統(tǒng)計(jì),在所有的瀏覽器中,修改后的版本都運(yùn)行的更快(優(yōu)化幅度最明顯的是IE8,使用后者比使用前者快273倍)。

HTML元素集合

HTML元素集合是包含了DOM節(jié)點(diǎn)引用的類數(shù)組對象。
可以用以下方法或?qū)傩缘玫揭粋€(gè)HTML元素集合:

document.getElementsByName()

document.getElementsByTagName()

document.getElementsByClassName()

document.images 頁面中所有img元素

document.links 頁面中所有a元素

document.forms 頁面中所有表單元素

document.forms[0].elements 頁面中第一個(gè)表單的所有字段

HTML元素集合處于一種“實(shí)時(shí)的狀態(tài)”,這意味著當(dāng)?shù)讓游臋n對象更新時(shí),它也會自動更新,也就是說,HTML元素集合與底層的文檔對象之間保持的連接。正因如此,每當(dāng)你想從HTML元素集合中獲取一些信息時(shí),都會產(chǎn)生一次查詢操作,這正是低效之源。

昂貴的集合
//這是一個(gè)死循環(huán)  
//不管你信不信,反正我是信了  
var alldivs = document.getElementsByTagName("div");  
for (var i = 0; i < alldivs.length; i++){  
    document.body.appendChild(document.createElement("div"));  
}

乍一看,這段代碼只是單純的把頁面中的div數(shù)量翻倍:遍歷所有的div,每次創(chuàng)建一個(gè)新的div并創(chuàng)建到添加到body中。
但事實(shí)上,這是一個(gè)死循環(huán):因?yàn)檠h(huán)的退出條件alldivs.length在每一次循環(huán)結(jié)束后都會增加,因?yàn)檫@個(gè)HTML元素集合反映的是底層文檔元素的實(shí)時(shí)狀態(tài)。
接下來,我們通過這段代碼,對一個(gè)HTML元素集合做一些處理:

function toArray(coll){  
    for (var i = 0, a = [], len = coll.lengthl i < len; i++){  
        a[i] = coll[i];  
    }  
    return a;  
}  
  
//將一個(gè)HTML元素集合拷貝到一個(gè)數(shù)組中  
var coll = document.getElementsByTagName("div");  
var arr = toArray(coll);  

現(xiàn)在比較以下兩個(gè)函數(shù):

function loopCollection(){  
    for (var count = 0; count < coll.length; count++){  
        //processing...  
    }  
}  
  
function loopCopiedArray(){  
    for (var count = 0; count < arr.length; count++){  
        //processing...  
    }  
}

在IE6中,后者比前者快114倍;IE7中119倍;IE8中79倍...
所以,在相同的內(nèi)容和數(shù)量下,遍歷一個(gè)數(shù)組的速度明顯快于遍歷一個(gè)HTML元素集合。
由于在每一次迭代循環(huán)中,讀取元素集合的length屬性會引發(fā)集合進(jìn)行更新,這在所有的瀏覽器中都有明顯的性能問題,所以你也可以這么干:

function loopCacheLengthCollection(){  
    var coll = document.getElementsByTagName("div"),  
        len = coll.length;  
    for (var count = 0; count < len; count++){  
        //processing...  
    }  
}

這個(gè)函數(shù)和上面的loopCopiedArray()一樣快。

訪問集合元素時(shí)使用局部變量

一般來說,對于任何類型的DOM訪問,當(dāng)同一個(gè)DOM屬性或者方法需要被多次訪問時(shí),最好使用一個(gè)局部變量緩存此成員。當(dāng)遍歷一個(gè)集合時(shí),首要優(yōu)化原則是把集合存儲在局部變量中,并把length緩存在循環(huán)外部,然后使用局部變量訪問這些需要多次訪問的元素。
一個(gè)栗子,在循環(huán)之中訪問每個(gè)元素的三個(gè)屬性。

function collectionGlobal(){  
    var coll = document.getElementsByTagName("div"),  
        len = coll.length,  
        name = "";  
    for (var count = 0; count < len; count++){  
        name = document.getElementsByTagName("div")[count].nodeName;  
        name = document.getElementsByTagName("div")[count].nodeType;  
        name = document.getElementsByTagName("div")[count].tagName;  
        //我的天不會有人真的這么寫吧...  
    }  
    return name;  
}

上面這段代碼,大家不要當(dāng)真...正常人肯定是寫不出來的...這里是為了對比一下,所以把這種最慢的情況寫給大家看。
接下來,是一個(gè)稍微優(yōu)化了的版本:

function collectionLocal(){  
    var coll = document.getElementsByTagName("div"),  
        len = coll.length,  
        name = "";  
    for (var count = 0; count < length; count++){  
        name = coll[count].nodeName;  
        name = coll[count].nodeType;  
        name = coll[count].tagName;  
    }  
    return name;  
}

這次就看起來正常很多了,最后是這次優(yōu)化之旅的最終版本:

function collectionNodesLocal(){  
    var coll = document.getElementsByTagName("div"),  
        len = coll.length,  
        name = "",  
        ele = null;  
    for (var count = 0; count < len; count++){  
        ele = coll[count];  
        name = ele.nodeName;  
        name = ele.nodeType;  
        name = ele.tagName;  
    }  
    return name;  
}
遍歷DOM 在DOM中爬行

通常你需要從某一個(gè)DOM元素開始,操作周圍的元素,或者遞歸查找所有的子節(jié)點(diǎn)。
考慮下面兩個(gè)等價(jià)的栗子:

//1  
function testNextSibling(){  
    var el = document.getElementById("mydiv"),  
        ch = el.firstChild,  
        name = "";  
    do {  
        name = ch.nodeName;  
    } while (ch = ch.nextSibling);  
    return name;  
}  
  
//2  
function testChildNodes(){  
    var el = document.getElementById("mydiv"),  
        ch = el.childNodes,  
        len = ch.length,  
        //childNodes是一個(gè)元素集合,因此在循環(huán)中主席緩存length屬性以避免迭代更新  
        name = "";  
    for (var count = 0; count < len; count++){  
        name = ch[count].nodeName;  
    }  
    return name;  
}

在不同瀏覽器中,兩種方法的運(yùn)行時(shí)間幾乎相等。但在老版本的IE瀏覽器中,nextSibling的性能比childNodes更好一些。

元素節(jié)點(diǎn)

我們知道,DOM節(jié)點(diǎn)有以下五種分類:

整個(gè)文檔是一個(gè)文檔節(jié)點(diǎn)

每個(gè)HTML元素是元素節(jié)點(diǎn)

HTML元素內(nèi)的文本是文本節(jié)點(diǎn)

每個(gè)HTML屬性是屬性節(jié)點(diǎn)

注釋是注釋節(jié)點(diǎn)

諸如childNodes、firstChild、nextSibling這些DOM屬性是不區(qū)分元素節(jié)點(diǎn)和其他類型的節(jié)點(diǎn)的,但往往我們只需要訪問元素節(jié)點(diǎn),此時(shí)需要做一些過濾的工作。事實(shí)上,這些類型檢查的過程都是不必要的DOM操作。
許多現(xiàn)代瀏覽器提供的API只返回元素節(jié)點(diǎn),如果可用的話推薦直接只用這些API,因?yàn)樗鼈兊膱?zhí)行效率比自己在js中過濾的效率要高。

現(xiàn)代瀏覽器提供的API(被替換的API)

children(childNodes)

childElementCount (childNodes.length)

firstElementChild (firstChild)

lastElementChild (lastChild)

nextElementSibling (nextSibling)

previousElementSibling (previousSibling)

使用這些新的API,可以直接獲取到元素節(jié)點(diǎn),也正是因此,其速度也更快。

選擇器API

有時(shí)候?yàn)榱说玫叫枰脑亓斜恚_發(fā)人員不得不組合調(diào)用getElementById、getElementsByTagName,并遍歷返回的節(jié)點(diǎn),但這種繁密的過程效率低下。
最新的瀏覽器提供了一個(gè)傳遞參數(shù)為CSS選擇器的名為querySelectorAll()的原生DOM方法。這種方式自然比使用js和DOM來遍歷查找元素要快的多。
比如,

var elements = document.querySelectorAll("#menu a");

這一段代碼,返回的是一個(gè)NodeList————包含著匹配節(jié)點(diǎn)的類數(shù)組對象。與之前不同的是,這個(gè)方法不會返回HTML元素集合,因此返回的節(jié)點(diǎn)不會對應(yīng)實(shí)時(shí)的文檔結(jié)構(gòu),也避免了之前由于HTML集合引起的性能(潛在邏輯)問題。
如果不使用querySelectorAll(),我們需要這樣寫:

var elements = document.getElementById("menu").getElementsByTagName("a");

不僅寫起來更麻煩了,更要注意的是,此時(shí)的elements是一個(gè)HTML元素集合,所以還需要把它c(diǎn)opy到數(shù)組中,才能得到一個(gè)與前者相似的靜態(tài)列表。
還有一個(gè)querySelector()方法,用來獲取第一個(gè)匹配的節(jié)點(diǎn)。

重繪與重排(Repaints & Reflows)

瀏覽器用來顯示頁面的所有“組件”,有:HTML標(biāo)簽、js、css、圖片——之后會解析并生成兩個(gè)內(nèi)部的數(shù)據(jù)結(jié)構(gòu):

DOM樹(表示頁面結(jié)構(gòu))

渲染樹(表示DOM節(jié)點(diǎn)應(yīng)該如何表示)

DOM樹中的每一個(gè)需要顯示的節(jié)點(diǎn)在渲染樹中至少存在一個(gè)對應(yīng)的節(jié)點(diǎn)。
渲染樹中的節(jié)點(diǎn)被稱為“幀(frames)”或“盒(boxes)”,符合css盒模型的定義,理解頁面元素為一個(gè)具有padding、margin、borders和position的盒子。
一旦渲染樹構(gòu)建完成,瀏覽器就開始顯示頁面元素,這個(gè)過程稱為繪制(paint)

當(dāng)DOM的變化影響了元素的幾何屬性(寬、高)——比如改變改變了邊框的寬度或者給一個(gè)段落增加一些文字導(dǎo)致其行數(shù)的增加——瀏覽器就需要重新計(jì)算元素的幾何屬性,同樣,頁面中其他元素的幾何屬性和位置也會因此受到影響。
瀏覽器會使渲染樹中收到影響的部分消失,重新構(gòu)建渲染樹,這個(gè)過程稱為“重排(reflow)”。重排完成之后,瀏覽器會重新將受到影響的部分繪制到瀏覽器中,這個(gè)過程稱之為“重繪(repaint)”。

如果改變的不是元素的幾何屬性,如:改變元素的背景顏色,不會發(fā)生重排,只會發(fā)生一次重繪,因?yàn)樵氐牟季植]有改變。
不管是重繪還是重排,都是代價(jià)昂貴的操作,它們會導(dǎo)致web應(yīng)用程序的UI反應(yīng)遲鈍,應(yīng)當(dāng)盡可能的減少這類過程的發(fā)生。

重排何時(shí)發(fā)生?

添加或刪除可見的DOM元素

元素位置的改變

元素尺寸的改變(padding、margin、border、height、width)

內(nèi)容改變(文本改變或圖片尺寸改變)

頁面渲染器初始化

瀏覽器窗口尺寸改變

滾動條的出現(xiàn)(會觸發(fā)整個(gè)頁面的重排)

最小化重繪和重排 改變樣式

一個(gè)栗子:

var el = document.getElementById("mydiv");  
el.style.borderLeft = "1px";  
el.style.borderRight = "2px";  
el.style.padding = "5px";  

示例中,元素的三個(gè)樣式被改變,而且每一個(gè)都會影響元素的幾何結(jié)構(gòu)。在最糟糕的情況下,這段代碼會觸發(fā)三次重排(大部分現(xiàn)代瀏覽器為此做了優(yōu)化,只會觸發(fā)一次重排)。從另一個(gè)角度看,這段代碼四次訪問DOM,可以被優(yōu)化。

var el = document.getElementById("mydiv");  
//思路:合并所有改變?nèi)缓笠淮涡蕴幚? 
//method_1:使用cssText屬性  
el.style.cssText = "border-left: 1px; border-right: 2px; padding: 5px";  
  
//method_2:修改類名  
el.className = "anotherClass";  
批量修改DOM

當(dāng)你需要對DOM元素進(jìn)行一系列操作的時(shí)候,不妨按照如下步驟:

使元素脫離文檔流

對其應(yīng)用多重改變

把元素帶回文檔中

上面的這一套組合拳中,第一步和第三部分別會觸發(fā)一次重排。但是如果你忽略了這兩個(gè)步驟,那么在第二步所產(chǎn)生的任何修改都會觸發(fā)一次重排

在此安利三種可以使DOM元素脫離文檔流的方法:

隱藏元素

使用文檔片段(document fragment)在當(dāng)前DOM之外構(gòu)建一個(gè)子樹,再把它拷貝回文檔

將原始元素拷貝到一個(gè)脫離文檔的節(jié)點(diǎn)中,修改副本,完成后再替換原始元素

讓動畫元素脫離文檔流

一般情況下,重排只影響渲染樹中的一小部分,但也可能影響很大的一部分,甚至是整個(gè)渲染樹。
瀏覽器所需的重排次數(shù)越少,應(yīng)用程序的響應(yīng)速度也就越快。
想象這樣一種情況,頁面的底部有一個(gè)動畫,會推移頁面整個(gè)余下的部分,這將是一次代價(jià)昂貴的大規(guī)模重排!用戶也勢必會感覺到頁面一卡一卡的。
因此,使用以下步驟可以避免頁面中的大部分重排:

使用絕對定位讓頁面上的動畫元素脫離文檔流

動畫展示階段

動畫結(jié)束時(shí),將元素恢復(fù)定位。

IE的:hover

從IE7開始,IE允許在任何元素上使用:hover這個(gè)css選擇器。
然而,如果你有大量元素使用了:hover,你會發(fā)現(xiàn),賊喇慢!

事件委托(Event Delegation)

這一個(gè)優(yōu)化手段也是在前端求職面試中的高頻題目。
當(dāng)頁面中有大量的元素,并且這些元素都需要綁定事件處理器。
每綁定一個(gè)事件處理器都是有代價(jià)的,要么加重了頁面負(fù)擔(dān),要么增加了運(yùn)行期的執(zhí)行時(shí)間。再者,事件綁定會占用處理時(shí)間,而且瀏覽器需要跟蹤每個(gè)事件處理器,這也會占用更多的內(nèi)存。還有一種情況就是,當(dāng)這些工作結(jié)束時(shí),這些事件處理器中的絕大多數(shù)都是不再需要的(并不是100%的按鈕或鏈接都會被用戶點(diǎn)擊),因此有很多工作是沒有必要的。
事件委托的原理很簡單——事件逐層冒泡并能被父級元素捕獲
使用事件委托,只需要給外層元素綁定一個(gè)處理器,就可以處理在其子元素上觸發(fā)的所有事件。
有以下幾點(diǎn)需要注意:

訪問事件對象,判斷事件源

按需取消文檔樹中的冒泡

按需阻止默認(rèn)動作

小結(jié)

訪問和操作DOM需要穿越連接ECMAScript和DOM兩個(gè)島嶼之間的橋梁,為了盡可能的減少“過橋費(fèi)”,有以下幾點(diǎn)需要注意:

最小化DOM訪問次數(shù)

對于需要多次訪問的DOM節(jié)點(diǎn),使用局部變量存儲其引用

如果要操作一個(gè)HTML元素集合,建議把它拷貝到一個(gè)數(shù)組中

使用速度更快的API:比如querySelectorAll

留意重排和重繪的次數(shù)

事件委托

分享前端和一些有趣的東西

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/81420.html

相關(guān)文章

  • javascript性能優(yōu)化技巧

    摘要:語句會使得局部變量位于作用域第二層,會使性能下降,所以應(yīng)避免使用。使用事件委托來減少事件處理器的數(shù)量。把最可能出現(xiàn)的條件放在首位。在進(jìn)行優(yōu)化時(shí),要弄清楚性能瓶頸,然后對癥優(yōu)化。新看到一篇很棒的文章前端性能優(yōu)化備忘錄如有不對,歡迎指正。 春節(jié)在家,把《高性能的JavaScript》刷了一遍,受益匪淺。本著每看完一本書都要做讀書筆記的習(xí)慣,將書中的知識點(diǎn)總結(jié)一下。 由于不同瀏覽器使用的Ja...

    levinit 評論0 收藏0
  • 高性能的網(wǎng)頁開發(fā)概要

    摘要:或者說一直以來我是缺乏開發(fā)高性能網(wǎng)頁的意識的,但是想做一個(gè)好的前端開發(fā)者,是需要在當(dāng)自己編寫的程序慢慢復(fù)雜以后還能繼續(xù)保持網(wǎng)頁的高性能的。 不知道有多少人和我一樣,在以前的開發(fā)過程中很少在乎自己編寫的網(wǎng)頁的性能。或者說一直以來我是缺乏開發(fā)高性能網(wǎng)頁的意識的,但是想做一個(gè)好的前端開發(fā)者,是需要在當(dāng)自己編寫的程序慢慢復(fù)雜以后還能繼續(xù)保持網(wǎng)頁的高性能的。這需要我們對JavaScript語句,...

    bovenson 評論0 收藏0
  • 【讀書筆記】《高性能JavaScript》

    摘要:性能訪問字面量和局部變量的速度是最快的,訪問數(shù)組和對象成員相對較慢變量標(biāo)識符解析過程搜索執(zhí)行環(huán)境的作用域鏈,查找同名標(biāo)識符。建議將全局變量存儲到局部變量,加快讀寫速度。優(yōu)化建議將常用的跨作用域變量存儲到局部變量,然后直接訪問局部變量。 缺陷 這本書是2010年出版的,這本書談性能是有時(shí)效性的,現(xiàn)在馬上就2018年了,這幾年前端發(fā)展的速度是飛快的,書里面還有一些內(nèi)容考慮IE6、7、8的東...

    chengjianhua 評論0 收藏0
  • 高性能JavaScript(文檔)

    摘要:最近在全力整理高性能的文檔,并重新學(xué)習(xí)一遍,放在這里方便大家查看并找到自己需要的知識點(diǎn)。 最近在全力整理《高性能JavaScript》的文檔,并重新學(xué)習(xí)一遍,放在這里方便大家查看并找到自己需要的知識點(diǎn)。 前端開發(fā)文檔 高性能JavaScript 第1章:加載和執(zhí)行 腳本位置 阻止腳本 無阻塞的腳本 延遲的腳本 動態(tài)腳本元素 XMLHTTPRequest腳本注入 推薦的無阻塞模式...

    RayKr 評論0 收藏0
  • 又被事件冒泡坑了一把,這次要徹底弄懂瀏覽器的事件流

    摘要:事件冒泡一個(gè)簡單,但是坑了我無數(shù)回的知識點(diǎn)與的交互通過事件來實(shí)現(xiàn)。而瀏覽器的事件流是一個(gè)非常重要的概念。不去討論那些古老的瀏覽器有事件捕獲與事件冒泡的爭議,只需要知道在中規(guī)定的事件流包括了三個(gè)部分,事件捕獲階段處于目標(biāo)階段事件冒泡階段。 打算封裝一個(gè)彈窗組件,做的時(shí)候忘記了考慮事件冒泡的因素,結(jié)果被坑得不要不要的。為了解決自己的問題,去查閱了不少資料,把事件流相關(guān)的知識都給總結(jié)一下。 ...

    Ocean 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<