成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

高性能JavaScript閱讀簡(jiǎn)記(二)

RaoMeng / 1031人閱讀

摘要:訪問集合元素時(shí)使用局部變量對(duì)于任何類型的訪問,如果對(duì)同一個(gè)屬性或者方法訪問多次,最好使用一個(gè)局部變量對(duì)此成員進(jìn)行緩存。

三、DOM Scripting DOM編程

我們都知道對(duì)DOM操作的代價(jià)昂貴,這往往成為網(wǎng)頁應(yīng)用中的性能瓶頸。在解決這個(gè)問題之前,我們需要先知道什么是DOM,為什么他會(huì)很慢。

DOM in the Browser World 瀏覽器中的DOM

DOM是一個(gè)獨(dú)立于語言的,使用XMLHTML文檔操作的應(yīng)用程序接口(API)。瀏覽器中多與HTML文檔打交道,DOM APIs也多用于訪問文檔中的數(shù)據(jù)。而在瀏覽器的實(shí)現(xiàn)中,往往要求DOM和JavaScript相互獨(dú)立。例如在IE中,JavaScript的實(shí)現(xiàn)位于庫文件jscript.dll中,而DOM的實(shí)現(xiàn)位于另一個(gè)庫mshtml.dll中(內(nèi)部代號(hào)Trident),這也是為什么IE內(nèi)核是Trident,IE前綴為-ms-,當(dāng)然,我們說的瀏覽器內(nèi)核其實(shí)英文名叫 Rendering Engine/Layout Engine,準(zhǔn)確翻譯應(yīng)該是渲染引擎/排版引擎/模板引擎(其實(shí)是一個(gè)東西);另外一個(gè)就是JavaScript引擎,ie6-8采用的是JScript引擎,ie9采用的是Chakra。而這是相分離的;Chrome中Webkit的渲染引擎和V8的JavaScript引擎,F(xiàn)irefox中Spider-MonkeyJavaScript引擎和Gecko的渲染引擎,都是相互分離的。
Inherently Slow 天生就慢
因?yàn)樯衔乃f的,瀏覽器渲染引擎JavaScript引擎是相互獨(dú)立的,那么兩者之間以功能接口相互連接就會(huì)帶來性能損耗。曾有人把DOMECMAScript(JavaScript)比喻成兩個(gè)島嶼,之間以一座收費(fèi)橋連接,每次ECMAScript需要訪問DOM時(shí),都需要過橋,交一次“過橋費(fèi)”,操作DOM的次數(shù)越多,費(fèi)用就越高,這里的費(fèi)用我們可以看作性能消耗。因此請(qǐng)盡力減少操作DOM的次數(shù)。
1. DOM Access and Modification DOM訪問和修改
訪問DOM的代價(jià)昂貴,修改DOM的代價(jià)可能更貴,因?yàn)樾薷臅?huì)導(dǎo)致瀏覽器重新計(jì)算頁面的幾何變化,更更更貴的是采用循環(huán)訪問或者修改元素,特別是在HTML集合中進(jìn)行循環(huán)。簡(jiǎn)單舉例:

function innerHTMLLoop(){
    for ( var count = 0; count < 100000; count++){
        document.getElementById("p").innerHTML += "-";
    }
}

這時(shí)候,每執(zhí)行一次for循環(huán),就對(duì)DOM進(jìn)行了一次讀操作和寫操作(訪問和修改);此時(shí)我們可以采用另外一種方式:

function innerHTMLLoop(){
    var content = "";
    for ( var count = 0; count < 100000; count++){
        content += "-";
    }
    document.getElementById("p").innerHTML += content;
}

我們使用了一個(gè)局部變量存儲(chǔ)更新后的內(nèi)容,在循環(huán)結(jié)束時(shí)一次性寫入,這時(shí)候只執(zhí)行了一次讀操作和寫操作,性能提升顯著。因此,盡量少的操作DOM,如果可以在JavaScript范圍內(nèi)完成的話。
2. innerHTML Versus DOM methods innerHTML與DOM方法
在老版本瀏覽器中,innerHTML更快但差別不大,更新的瀏覽器中,不相上下,最新的瀏覽器中,DOM方法更快,但依然差別不大。
3. Cloning Nodes 節(jié)點(diǎn)克隆
這樣的方法和DOM方法操作速度不相上下。

HTML Collections HTML集合

HTMLCollection是用于存放DOM節(jié)點(diǎn)引用的類數(shù)組對(duì)象。得到的方法有:document.getElementByName/document.getElementByClassName/document.getElementByTagName/document.querySelectAll/document.images/document.links/document.forms等;也有類似于document.forms[0].elements(頁面第一個(gè)表單的所有字段)等。
上面這些方法返回HTMLCollection對(duì)象,是一種類似數(shù)組的列表,沒有數(shù)組的方法,但是有l(wèi)enth屬性。在DOM標(biāo)準(zhǔn)中定義為:“虛擬存在,意味著當(dāng)?shù)讓游臋n更新時(shí),他們將自動(dòng)更新”。HTML集合實(shí)際上會(huì)去查詢文檔,更新信息時(shí),每次都要重復(fù)執(zhí)行這種查詢操作,這正是低效率的來源。

Expensive collections 昂貴的集合
先看個(gè)例子:

var oDiv = document.getElementByTagName("div");
for (var i = 0; i < oDiv.length; i++){
    document.body.appendChild(document.createElement("div"))
}

好吧,這是個(gè)死循環(huán),永遠(yuǎn)不會(huì)結(jié)束,但是這個(gè)過程中,每訪問一次oDiv.length,就會(huì)重新計(jì)算一遍其長(zhǎng)度,當(dāng)然,前提是對(duì)所有的div重新進(jìn)行一次遍歷,因此,在這里,我們最好使用一個(gè)變量暫存oDIv.length

var oDivLen = document.getElementByTagName("div").length;
for (var i = 0; i < oDivLen; i++){
    document.body.appendChild(document.createElement("div"))
}

從性能角度來講,這樣做會(huì)快很多。同時(shí),因?yàn)閷?duì)HTML集合的訪問比對(duì)數(shù)組訪問要更耗費(fèi)性能,因此在某些不得不多次訪問HTML集合的情況下,可以先將集合存儲(chǔ)為一個(gè)數(shù)組,然后對(duì)數(shù)組進(jìn)行訪問:

function toArray(htmlList){
    for (var i = 0, htmlArray = [], len = htmlList.length; i < len; i++){
        htmlArray[i] = htmlList[i];
    }
    return htmlArray;
}

當(dāng)然,這也需要額外的開銷,需要自己進(jìn)行權(quán)衡是否有必要這樣做。

Local variables when accessing collection elements 訪問集合元素時(shí)使用局部變量
對(duì)于任何類型的DOM訪問,如果對(duì)同一個(gè)DOM屬性或者方法訪問多次,最好使用一個(gè)局部變量對(duì)此DOM成員進(jìn)行緩存。特別是在HTML集合中訪問元素時(shí),如果多次對(duì)集合中的某一元素訪問,同樣需要將這個(gè)元素先進(jìn)行緩存。

Walking the DOM DOM漫談

DOM API提供了多種訪問文檔結(jié)構(gòu)特定部分的方法,去選擇最有效的API。

Crawling the DOM 抓取DOM
如果你可以通過:document.getElementByID();獲得某元素就不要去用document.getElementById().parentNode;這么麻煩去獲取。如果你可已通過nextSibling去獲取一個(gè)元素就不要通過childNodes去獲取,因?yàn)楹笳呤且粋€(gè)nodeList集合。

Element nodes 元素節(jié)點(diǎn)
DOM包含三個(gè)節(jié)點(diǎn)(也可以說是四個(gè)):元素節(jié)點(diǎn)、屬性節(jié)點(diǎn)、文本節(jié)點(diǎn)(以及注釋節(jié)點(diǎn));通常情況下,我們獲取到和使用的是元素節(jié)點(diǎn),但是我們通過childNodes、firstChild、nextSibling等方法獲取到的是所有節(jié)點(diǎn)的屬性,js中有一些其他的API可以用來只返回元素節(jié)點(diǎn)或者元素節(jié)點(diǎn)的某些屬性,我們可以用這些API取代那些返回整個(gè)全部節(jié)點(diǎn)或者節(jié)點(diǎn)屬性的API,例如:

childNodes                        children
childNodes.length                childElementCount
firstChild                        firstElementChild
lastChild                        lastElementChild
nextSibling                        nextElementSibling
previousSibling                    previousElementSibling

在所有的瀏覽器中,后者比前者要快,只不過IE中后面部分方法并不支持,比如IE6/7/8,只支持children方法。

The Selectors API 選擇器API
傳統(tǒng)的選擇器在性能方面問題不大,只不過應(yīng)用場(chǎng)景相對(duì)單一,當(dāng)我們用習(xí)慣了CSS選擇器之后,我們會(huì)覺得DOM給我們提供的選擇器讓我們抓狂。在querySelector/querySelectorAll之前,如果我們想要查找到元素下符合條件的另一元素時(shí),不得不使用類似下面的方法:document.getElementById("id1").getElementById("id2");,但如果你想獲取一個(gè)類名為class1或類名為class2的div的時(shí)候,不得不這么處理:

function getDivClass1(className1,className2){
    var results = [];
    divs = document.getElementByTagName("div");
    for (var i = 0,len = divs.length; i < len; i++){
        _className = divs[i].className;
        if(_className === className1 || _className === className2){
            results.push(divs[i]);
        }
    }
    return results;
}

不僅僅是因?yàn)槿唛L(zhǎng)的代碼,多次對(duì)DOM進(jìn)行遍歷帶來的性能問題也不可小窺;不過在有了querySelector/querySelectorAll之后,這一切變得簡(jiǎn)單,減少對(duì)DOM的遍歷也帶來了性能的提升。上面兩個(gè)例子可以重寫如下:

document.querySelector("#id1 #id2");
document.querySelectorAll("div.className1,div.className2");

因此,如果可以,盡量使用querySelector/querySelectorAll吧。

Repaints and Reflows 重繪和重排(也稱回流)

這涉及到一個(gè)比較古老的議題,瀏覽器在拿到服務(wù)器響應(yīng)時(shí)都干了什么。我查閱了相當(dāng)一部分資料(網(wǎng)上很多地方說法是不準(zhǔn)確的,包括一些問答、博客),去了解整個(gè)流程,這里簡(jiǎn)單的描述一下過程。更多細(xì)節(jié)可參考《瀏覽器的工作原理:新式網(wǎng)絡(luò)瀏覽器幕后揭秘》,原版地址(http://taligarsiel.com/Projec...)。
上文提到過,瀏覽器的實(shí)現(xiàn)一般包括渲染引擎JavaScript引擎;二者是相互獨(dú)立的。
我們先從渲染引擎的角度來看一下在拿到服務(wù)器的文檔后的處理流程:

Parsing HTML to construct the DOM tree 解析HTML以構(gòu)建DOM tree
解析HTML文檔,將各個(gè)標(biāo)記逐個(gè)轉(zhuǎn)化為DOM tree 上的DOM節(jié)點(diǎn);當(dāng)然并不是一一對(duì)應(yīng);類似于head這樣的標(biāo)記是在DOM tree上是沒有對(duì)應(yīng)的節(jié)點(diǎn)的。在這個(gè)過程中,同時(shí)被解析的還包括外部CSS文件以及樣式元素中的樣式數(shù)據(jù),這些數(shù)據(jù)信息被準(zhǔn)備好進(jìn)行下一步工作。

Render tree construction 構(gòu)建render tree
DOM tree構(gòu)建過程中,CSS文件同時(shí)被解析,DOM tree上每一個(gè)節(jié)點(diǎn)的對(duì)應(yīng)的顏色尺寸等信息被保存在另一個(gè)被稱作rules tree的對(duì)象中(具體實(shí)現(xiàn)方式webkitgecho是不一樣的,可參考上文提到過的《瀏覽器的工作原理》)。DOM treerules tree兩者一一對(duì)應(yīng),均構(gòu)建完成之后,render tree也就構(gòu)建完成了。

Layout of the render tree 布局render tree
依據(jù)render tree中的節(jié)點(diǎn)信息和對(duì)應(yīng)的rules中的尺寸信息(包括display屬性等),為每一個(gè)節(jié)點(diǎn)分配一個(gè)應(yīng)該出現(xiàn)在屏幕上的確切坐標(biāo)。

Painting the render tree 繪制render tree
就是將已布局好的節(jié)點(diǎn),加上對(duì)應(yīng)的顏色等信息,繪制在頁面上。

當(dāng)然,瀏覽器并不會(huì)等到全部的HTML文檔信息都拿到之后才進(jìn)行解析,也不會(huì)等到全部解析完畢之后才會(huì)進(jìn)行構(gòu)建render tree設(shè)置布局。渲染引擎可能在接收到一部分文檔后就開始解析,在解析了一部分文檔后就開始進(jìn)行構(gòu)建render treelayout render tree。
JavaScript引擎的工作:
正常的流程中,渲染引擎在遇到

閱讀需要支付1元查看
<