讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議152:使用局部變量存儲數據 >

建議152:使用局部變量存儲數據

不論從性能的角度來看,還是從功能的角度來看,作用域概念都是理解JavaScript的關鍵。作用域對JavaScript有許多影響,從確定哪些變量可以被函數訪問,到確定this的值。JavaScript作用域也關係到性能,但要理解速度與作用域的關係,首先要理解作用域的工作原理。

(1)作用域鏈和標識符解析

每一個JavaScript函數都被表示為對象。函數對像如其他對像一樣,擁有可以編程訪問的屬性和一系列不能被程序訪問僅供JavaScript引擎使用的內部屬性。其中一個內部屬性是scope,由ECMA-262標準第三版定義。

內部屬性scope包含一個函數被創建的作用域中對象的集合。此集合被稱為函數的作用域鏈,它決定哪些數據可由函數訪問。此函數作用域鏈中的每個對象被稱為一個可變對象,每個可變對象都以「鍵-值對」的形式存在。在一個函數被創建後,它的作用域鏈被填充以對象,這些對像代表創建此函數的環境中可訪問的數據。例如,下面這個全局函數:


function add(num1,num2){

var sum=num1+num2;

return sum;

}


在add函數被創建後,它的作用域鏈中填入一個單獨的可變對象,此全局對像代表了所有在全局範圍內定義的變量。此全局對像包含諸如窗口、瀏覽器和文檔之類的訪問接口。Add函數的作用域鏈將會在運行時用到。例如,下面的代碼:


var total=add(5,10);


在運行add函數時將建立一個內部對象,稱之為「運行期上下文」。一個運行期上下文定義了一個函數運行時的環境。對函數的每次運行而言,由於每個運行期上下文都是獨一的,所以多次調用同一個函數就會導致多次創建運行期上下文。在函數執行完畢時運行期上下文就被銷毀。

一個運行期上下文有它自己的作用域鏈,用於標識符解析。當運行期上下文被創建時,它的作用域鏈被初始化,連同運行函數的scope屬性中所包含的對象也被初始化。scope屬性值按照它們出現在函數中的順序,被複製到運行期上下文的作用域鏈中。這項工作一旦完成,一個被稱做「激活對像」的新對象就為運行期上下文創建好了。此激活對像作為函數執行期的一個可變對象,包含訪問所有局部變量、命名參數、參數集合和this的接口。然後,此對像被推入作用域鏈的前端。當作用域鏈被銷毀時,激活對象也一同被銷毀。

在函數運行過程中,每遇到一個變量,標識符識別過程要決定從哪裡獲得或存儲數據。在此過程中搜索運行期上下文的作用域鏈,查找同名的標識符。搜索工作從運行函數的激活目標的作用域鏈的前端開始。如果找到了,那麼就使用這個具有指定標識符的變量;如果沒找到,那麼搜索工作將進入作用域鏈的下一個對象。此過程持續運行,直到標識符被找到,或者沒有更多對象可以搜索(在這種情況下標識符將被認為是未定義的)。在函數運行時每個標識符都要經過這樣的搜索過程。例如,在上面示例中,函數訪問sum、num1、num2時都會產生這樣的搜索過程。

(2)標識符識別性能

標識符識別不是「免費」的。事實上沒有哪種計算機操作可以不產生性能開銷。在運行期上下文的作用域鏈中,一個標識符所處的位置越深,它的讀寫速度就越慢。因此,在函數中,局部變量的訪問速度總是最快的,全局變量通常是最慢的。全局變量總是處於運行期上下文作用域鏈的最後一個位置上,總是最後才被訪問到。

總的趨勢是,對所有瀏覽器來說,一個標識符所處的位置越深,讀寫它的速度就越慢。採用了優化的JavaScript引擎的瀏覽器,如Safari 4,在訪問域外標識符時沒有這種性能損失,而IE和其他瀏覽器則有較大幅度的性能損失。值得注意的是,早期瀏覽器(如IE 6和Firefox 2)將會耗費更多的時間訪問域外變量。

通過以上內容可以瞭解,在沒有優化JavaScript引擎的瀏覽器中,最好盡可能使用局部變量。一個好的經驗法則:用局部變量存儲本地範圍之外的變量值,如果它們在函數中的使用多於一次。


function initUI{

var bd=document.body,links=document.getElementsByTagName_r(\"a\"),i=0,len=links.length;

while(i<len){

update(links[i++]);

}

document.getElementById(\"go-btn\").onclick=function{

start;

};

bd.className=\"active\";

}


上面示例中的函數包含3個對document的引用。document是一個全局對象,搜索此變量,必須遍歷整個作用域鏈,直到在全局變量對像中找到它。可以通過這種方法減輕重複的全局變量訪問對性能的影響:首先將全局變量的引用存儲在一個局部變量中,然後使用這個局部變量代替全局變量。例如,上面的代碼可以這樣重寫:


function initUI{

var doc=document,bd=doc.body,links=doc.getElementsByTagName_r(\"a\"),i=0,len=links.length;

while(i<len){

update(links[i++]);

}

doc.getElementById(\"go-btn\").onclick=function{

start;

};

bd.className=\"active\";

}


在上面代碼中,首先將document的引用存入局部變量doc中。現在訪問全局變量的次數是1次,而不是3次。用doc替代document更快,因為它是一個局部變量。當然,因為數量的原因,這個簡單的函數不會顯示出巨大的性能改進,不過可以想像一下,如果幾十個全局變量被反覆訪問,那麼性能改進顯然會很明顯。