數據存儲在哪裏,關係到代碼運行期間數據被檢索到的速度。在JavaScript中,此問題相對簡單,由於數據存儲只有少許方式可供選擇。正如其餘語言那樣,數據存儲位置關係到訪問速度。在JavaScript中有四種基本的數據訪問位置:前端
1.Literal values 直接量web
直接量僅僅表明本身,而不存儲於特定位置。 JavaScript的直接量包括:字符串,數字,布爾值,對象,數組,函數,正則表達式,具備特殊意義的空值,以及未定義。正則表達式
2.Variables 變量編程
開發人員使用var關鍵字建立用於存儲數據值。數組
3.Array items 數組項瀏覽器
具備數字索引,存儲一個JavaScript數組對象。函數
4.Object members 對象成員性能
具備字符串索引,存儲一個JavaScript對象。優化
每一種數據存儲位置都具備特定的讀寫操做負擔。大多數狀況下,對一個直接量和一個局部變量數據訪問的性能差別是微不足道的。訪問數組項和對象成員的代價要高一些,具體高多少,很大程度上依賴於瀏覽器。總的來講,直接量和局部變量的訪問速度要快於數組項和對象成員的訪問速度。,若是關心運行速度,那麼儘可能使用直接量和局部變量,限制數組項和對象成員的使用。this
1.做用域鏈和標識符解析
每個JavaScript函數都被表示爲對象。進一步說,它是一個函數實例。函數對象正如其餘對象那樣,擁有你能夠編程訪問的屬性,和一系列不能被程序訪問,僅供JavaScript引擎使用的內部屬性。其中一個內部屬性是[[Scope]],由ECMA-262標準第三版定義。內部[[Scope]]屬性包含一個函數被建立的做用域中對象的集合。此集合被稱爲函數的做用域鏈,它決定哪些數據可由函數訪問。此函數做用域鏈中的每一個對象被稱爲一個可變對象,每一個可變對象都以「鍵值對」的形式存在。當一個函數建立後,它的做用域鏈被填充以對象,這些對象表明建立此函數的環境中可訪問的數據。
當代碼在一個環境中執行時,會建立變量對象的一個做用域鏈(scope chain,不簡稱sc)來保證對執行環境有權訪問的變量和函數的有序訪問。做用域第一個對象始終是當前執行代碼所在環境的變量對象(VO)
例如:
function add(x,y){
var b=x+y;
return b;
}
當add()函數建立後,它的做用域鏈中填入一個單獨的可變對象,此全局對象表明了全部全局範圍定義的變量。此全局對象包含諸如窗口、瀏覽器和文檔之類的訪問接口。
如圖:
上圖就是函數Add()的做用域鏈。
Add函數的做用域鏈將在運行時用到。若是運行下面的代碼
var total = add(5, 10);
運行此add函數時創建一個內部對象,稱做「運行期上下文」。一個運行期上下文定義了一個函數運行時的環境。對函數的每次運行而言,每一個運行期上下文都是獨一的。因此每次調用同一個函數就會致使多處調用上下文。當函數執行完畢,運行期上下文就被銷燬。
一個運行期上下文有它本身的做用域鏈,用於標示符解析。當運行期上下文被建立時,它的做用域被初始化,連同運行函數的[[Scope]]屬性中所包含的對象。這些值按照它們出如今函數中的順序,被複制到運行期上下文的做用域鏈中。這項工做一旦完成,一個被稱做「激活對象」的新對象就爲運行期上下文建立好了。此激活對象做爲函數執行期的一個可變對象,包含訪問全部局部變量,命名參數,參數集合,和this的接口,而後,這個對象被推入做用域的前端。看成用域鏈被銷燬時,激活對象也一同銷燬。
上圖是運行時Add()函數的做用域鏈。
在函數運行過程當中,沒遇到一個變量,標識符識別過程要決定從哪裏得到或者存儲數據,此過程收索運行期上下文的做用域鏈,查找同名的標識符。搜索工做從運行函數的激活目標之做用域鏈的前端開始。若是找到了,那麼就使用這個具備指定標識符的變量,若是沒有找到,搜索工做將進入做用域鏈的下一個對象。此過程持續進行,直到找到標示符。若是整個過程都沒有找到那麼被認爲是undefined。正是這種搜索過程影響了性能。
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"; }
此函數包括三個對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";
}
initUI()的新版本首先將document的引用存入局部變量doc中,如今訪問全局變量的次數是1次,而不是3次。用doc替代document更快,由於它是一個局部變量。固然,這個簡單的函數不回顯示出巨大的性能改進,由於數量的緣由。不過若是幾十個全局變量被反覆訪問,那麼性能改進將明顯的多麼出色。
3.改變做用域鏈
通常來講,一個運行期上下文的做用域鏈不會忽然被改變。可是,有兩種表達式能夠在運行時臨時改變運行期上下文做用域鏈。第一個是with表達式。
with表達式爲全部的對象屬性建立了一個默認操做變量。在其餘語言中,相似的功能一般用來避免書寫一些重複的代碼。initUI()函數能夠重寫成以下樣式:
function initUI(){ with (document){ //avoid! var bd = body, links = getElementsByTagName_r("a"), i = 0, len = links.length; while(i < len){ update(links[i++]); } getElementById("go-btn").onclick = function(){ start(); }; bd.className = "active"; } }
此重寫的initUI()版本使用了一個with表達式,避免屢次書寫document,這看起來彷佛更有效率,而實際上卻產生了一個性能問題。
當代碼流執行到一個with表達式時,運行期上下文的做用域鏈被臨時改變了。一個新的可變對象將被建立,她包含指定對象的全部屬性。此對象被插入到做用域鏈的前端,意味着如今函數的全部局部變量都被推入第二個做用域鏈對象中,因此訪問代價更高了。
經過將document對象傳遞給with表達式,一個新的可變對象容納了document對象的全部屬性,被插入到做用域鏈的前端。這使得訪問document的屬性很是快,可是訪問局部變量的速度卻變慢了,例如bd變量。正由於這個緣由,最好不要使用with表達式。正如前面提到的,只要簡單地將document存儲在一個局部變量中,就能夠得到性能上的提高。
在JavaScript中不僅是with表達式人爲地改變運行期上下文的做用域鏈,try-catch表達式的catch子句具備相同效果。當try塊發生錯誤時,程序流程自動轉入catch塊,並將異常對象推入做用域鏈前端的一個可變對象中。在catch塊中,函數的全部局部變量如今被放在第二個做用域鏈對象中。例如:
try {
methodThatMightCauseAnError();
} catch (ex){
alert(ex.message); //scope chain is augmented here
}
可是,只要catch子句執行完畢,做用域鏈就會返回到原來的狀態。
若是使用得當,try-catch表達式是很是有用的語句,因此不建議徹底避免。若是你計劃使用一個try-catch語句,請確保你瞭解可能發生的錯誤。一個try-catch語句不該該做爲JavaScript錯誤的解決辦法。若是你知道一個錯誤會常常發生,那說明應當修正代碼自己的問題。
你能夠經過精縮代碼的辦法最小化catch子句對性能的影響。一個很好的模式是將錯誤交給一個專用函數來處理。如:
try {
methodThatMightCauseAnError();
} catch (ex){
handleError(ex); //delegate to handler method
}
handleError()函數是catch子句中運行的惟一代碼。此函數以適當方法自由地處理錯誤,並接收由錯誤產生的異常對象。因爲只有一條語句,沒有局部變量訪問,做用域鏈臨時改變就不會影響代碼的性能。