在 JavaScript 中,數據存儲位置能夠對代碼總體性能產生重要影響。有四種數據訪問類型:直接量,變量,數組項,對象成員。
直接量僅僅表明本身,而不存儲於特定位置。 JavaScript 的直接量包括:字符串,數字,布爾值,對象,數組,函數,正則表達式,具備特殊意義的空值,以及未定義。
變量是指開發人員使用 var關鍵字建立用於存儲數據值。
數組現是指具備數字索引,存儲一個 JavaScript數組對象。
對象成員具備字符串索引,存儲一個 JavaScript 對象。
它們有不一樣的性能考慮。 直接量和局部變量訪問速度很是快,數組項和對象成員須要更長時間。 局部變量比域外變量快,由於它位於做用域鏈的第一個對象中。變量在做用域鏈中的位置越深,訪問所需的時間就越長。全局變量老是最慢的,由於它們老是位於做用域鏈的最後一環。
做用域鏈和標識符解析
每個 JavaScript 函數都被表示爲對象。進一步說,它是一個函數實例。函數對象正如其餘對象那樣,擁有你能夠編程訪問的屬性,和一系列不能被程序訪問,僅供 JavaScript 引擎使用的內部屬性。其中一個內部屬性是[[Scope]],由ECMA-262 標準第三版定義。
內部[[Scope]]屬性包含一個函數被建立的做用域中對象的集合。此集合被稱爲函數的做用域鏈,它決定哪些數據可由函數訪問。此函數做用域鏈中的每一個對象被稱爲一個可變對象,每一個可變對象都以「鍵值對」的形式存在。當一個函數建立後,它的做用域鏈被填充以對象,這些對象表明建立此函數的環境中可訪問的數據。例以下面這個全局函數:
function add(num1, num2){ var sum = num1 + num2; return sum; }
當 add()函數建立後,它的做用域鏈中填入一個單獨的可變對象,此全局對象表明了全部全局範圍定義的變量。此全局對象包含諸如窗口、瀏覽器和文檔之類的訪問接口。圖指出它們之間的關係(注意:此圖中只畫出全局變量中不多的一部分,其餘部分還不少)。
運行此 add 函數時創建一個內部對象,稱做「運行期上下文」。一個運行期上下文定義了一個函數運行時的環境。對函數的每次運行而言,每一個運行期上下文都是獨一的,因此屢次調用同一個函數就會致使屢次建立運行期上下文。當函數執行完畢,運行期上下文就被銷燬。
一個運行期上下文有它本身的做用域鏈,用於標識符解析。當運行期上下文被建立時,它的做用域鏈被初始化,連同運行函數的[[Scope]]屬性中所包含的對象。這些值按照它們出如今函數中的順序,被複制到運行期上下文的做用域鏈中。這項工做一旦完成,一個被稱做「激活對象」的新對象就爲運行期上下文建立好了。此激活對象做爲函數執行期的一個可變對象,包含訪問全部局部變量,命名參數,參數集合,和 this的接口。而後,此對象被推入做用域鏈的前端。看成用域鏈被銷燬時,激活對象也一同銷燬。顯示了前面實例代碼所對應的運行期上下文和它的做用域鏈。
在函數運行過程當中,每遇到一個變量,標識符識別過程要決定從哪裏得到或者存儲數據。此過程搜索運行期上下文的做用域鏈,查找同名的標識符。搜索工做從運行函數的激活目標之做用域鏈的前端開始。若是找到了,那麼就使用這個具備指定標識符的變量;若是沒找到,搜索工做將進入做用域鏈的下一個對象。此過程持續運行,直到標識符被找到,或者沒有更多對象可用於搜索,這種狀況下標識符將被認爲是未定義的。函數運行時每一個標識符都要通過這樣的搜索過程,例如前面的例子中,函數訪問 sum,num1,num2時都會產生這樣的搜索過程。正是這種搜索過程影響了性能。
標識符識別不是免費的, 事實上沒有哪一種電腦操做能夠不產生性能開銷。 在運行期上下文的做用域鏈中,一個標識符所處的位置越深,它的讀寫速度就越慢。因此,函數中局部變量的訪問速度老是最快的,而全局變量一般是最慢的(優化的 JavaScript 引擎在某些狀況下能夠改變這種情況)。全局變量老是處於運行期上下文做用域鏈的最後一個位置,因此老是最遠才能觸及的。
通常來講,一個運行期上下文的做用域鏈不會被改變。可是,有兩種表達式能夠在運行時臨時改變運行期上下文做用域鏈。第一個是 with表達式。當代碼流執行到一個 with 表達式時,運行期上下文的做用域鏈被臨時改變了。一個新的可變對象將被建立,它包含指定對象的全部屬性。此對象被插入到做用域鏈的前端,意味着如今函數的全部局部變量都被推入第二個做用域鏈對象中,因此訪問代價更高了。 第二個是try-catch 表達式。當 try塊發生錯誤時,程序流程自動轉入 catch 塊,並將異常對象推入做用域鏈前端的一個可變對象中。在 catch 塊中,函數的全部局部變量如今被放在第二個做用域鏈對象中。因此,避免使用 with 表達式, 由於它改變了運行期上下文的做用域鏈。 並且應當當心對待 try-catch 表達式的 catch子句,由於它具備一樣效果。
嵌套對象成員會形成重大性能影響, 一個屬性或方法在原形鏈中的位置越深,訪問它的速度就越慢。 若是可能的話請避免使用它們。更確切地說,你應當當心地,只在必要狀況下使用對象成員。若是在同一個函數中你要屢次讀取同一個對象屬性,最好將它存入一個局部變量。以局部變量替代屬性,避免多餘的屬性查找帶來性能開銷。在處理嵌套對象成員時這點特別重要,它們會對運行速度產生難以置信的影響。
通常來講,你能夠經過這種方法提升 JavaScript代碼的性能:將常用的對象成員,數組項,和域外變量存入局部變量中。而後,訪問局部變量的速度會快於那些原始變量。
ps:以上內容總結於《高性能javascript編程》