局部變量也可以理解為在函數內部定義的變量,很明顯訪問局部變量要比域外的變量要快,因為它位於作用域鏈的第一個變量對像中(關於作用域鏈的介紹可以閱讀這篇文章)。變數在作用域鏈的位置越深,存取所需的時間就越長,全域變數總是最慢的,因為它們位於作用域鏈的最後一個變數物件。
每種資料類型的存取都需要付出點數效能代價,對於直接量和局部變數基本上都能消耗得起,而存取陣列項目和物件成員則要代價高點。下圖顯示了不同瀏覽器,分別對這四種資料類型進行了200'000次操作所花費的時間。
由上圖可以看出,要想優化程式碼的效能,那麼盡量使用直接量和局部變量,限制數組項和物件成員的存取次數(將物件成員用一個局部變數來保存)。
首先我們要先了解一下物件成員的存取過程。其實函數就是一個特殊的對象,所以對象成員的存取跟函數的內部變數的存取都差不多,都是基於鏈的查找,前者是原型鏈,後者是作用域鏈,只是怎麼個鏈法有點差別而已。
物件成員包含屬性和方法,如果該成員是函數就稱為方法,否則就稱為屬性。
JavaScript中的物件是基於原形(原形本身就是一個物件)的,原形是其他物件的基礎。當你實例化一個Object物件或其它JS的內建物件時(var obj=new Object() or var obj={}),實例obj的原形由後台自動建立,瀏覽器FF,safari,Chrome可透過obj. __proto__屬性(等同於Object.prototype)可以存取到這個原形,也正是因為這個原形,每一個實例都能共享原形物件的成員。如:
var book = { 〜〜〜>" Javascript Book",
getName = function(){
return this.name;
}
};
alert(book.to);
此程式碼中,book物件有兩個私有成員,分別是屬性name和方法getName。 book物件並沒有定義成員toString,但呼叫了也沒有拋出錯誤,原因是book物件繼承了原形物件的成員。 book物件與原形的關係如下:
存取book物件成員toString的過程是這樣的,當book.toString()被呼叫時,後台對成員進行名為」toString」的搜索,首先從實例book本身開始,如果在book發現名為”toString」的成員,則搜索結束,否則繼續向__proto__指向的原型對象搜索,如果在Object的原形對像都找不到該成員,則表示該成員未定義。透過這種方式,book就可以存取它的原型物件所擁有的每個屬性或方法。
物件的另一高階用法就是模擬類別和繼承類,我喜歡叫這樣用法的物件為物件類別。繼承物件類別主要就是依賴原型鏈來完成的,這個知識點太多需要另外詳細 說明。透過上面的物件成員搜尋過程,存取物件成員的速度,隨著原型鏈的越深,搜尋的速度就越慢。下圖顯示了物件成員在原型鏈中所處的深度與存取時間的關 系:
由上圖可清楚的知道,每深入原型鏈一層都會增加性能的損失,所以像那種遍歷對象成員的操作開銷很大。還有另外一種常用且損耗效能的做法就是巢狀物件 成員(如window.location.href),像這種最好的做法就是減少點的次數了。例如location.href就比 window.location.href快。
好了,總結起來就一句話:一個屬性或方法在原型鏈的位置越深,訪問它的速度就越慢。解決方法就是:將經常使用的物件成員,將陣列項目和域外的變數存入局部變數中,然後存取這個局部變數。