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

資訊專欄INFORMATION COLUMN

高性能JavaScript閱讀簡記(二)

coolpail / 859人閱讀

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

三、DOM Scripting DOM編程

我們都知道對DOM操作的代價昂貴,這往往成為網頁應用中的性能瓶頸。在解決這個問題之前,我們需要先知道什么是DOM,為什么他會很慢。

DOM in the Browser World 瀏覽器中的DOM

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

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

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

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

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

HTML Collections HTML集合

HTMLCollection是用于存放DOM節點引用的類數組對象。得到的方法有:document.getElementByName/document.getElementByClassName/document.getElementByTagName/document.querySelectAll/document.images/document.links/document.forms等;也有類似于document.forms[0].elements(頁面第一個表單的所有字段)等。
上面這些方法返回HTMLCollection對象,是一種類似數組的列表,沒有數組的方法,但是有lenth屬性。在DOM標準中定義為:“虛擬存在,意味著當底層文檔更新時,他們將自動更新”。HTML集合實際上會去查詢文檔,更新信息時,每次都要重復執行這種查詢操作,這正是低效率的來源。

Expensive collections 昂貴的集合
先看個例子:

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

好吧,這是個死循環,永遠不會結束,但是這個過程中,每訪問一次oDiv.length,就會重新計算一遍其長度,當然,前提是對所有的div重新進行一次遍歷,因此,在這里,我們最好使用一個變量暫存oDIv.length

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

從性能角度來講,這樣做會快很多。同時,因為對HTML集合的訪問比對數組訪問要更耗費性能,因此在某些不得不多次訪問HTML集合的情況下,可以先將集合存儲為一個數組,然后對數組進行訪問:

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

當然,這也需要額外的開銷,需要自己進行權衡是否有必要這樣做。

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

Walking the DOM DOM漫談

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

Crawling the DOM 抓取DOM
如果你可以通過:document.getElementByID();獲得某元素就不要去用document.getElementById().parentNode;這么麻煩去獲取。如果你可已通過nextSibling去獲取一個元素就不要通過childNodes去獲取,因為后者是一個nodeList集合。

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

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

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

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

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;
}

不僅僅是因為冗長的代碼,多次對DOM進行遍歷帶來的性能問題也不可小窺;不過在有了querySelector/querySelectorAll之后,這一切變得簡單,減少對DOM的遍歷也帶來了性能的提升。上面兩個例子可以重寫如下:

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

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

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

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

Parsing HTML to construct the DOM tree 解析HTML以構建DOM tree
解析HTML文檔,將各個標記逐個轉化為DOM tree 上的DOM節點;當然并不是一一對應;類似于head這樣的標記是在DOM tree上是沒有對應的節點的。在這個過程中,同時被解析的還包括外部CSS文件以及樣式元素中的樣式數據,這些數據信息被準備好進行下一步工作。

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

Layout of the render tree 布局render tree
依據render tree中的節點信息和對應的rules中的尺寸信息(包括display屬性等),為每一個節點分配一個應該出現在屏幕上的確切坐標。

Painting the render tree 繪制render tree
就是將已布局好的節點,加上對應的顏色等信息,繪制在頁面上。

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

閱讀需要支付1元查看
<