做用域對性能的影響

做用域對性能的影響

做用域是理解JS的關鍵所在,一樣做用域關係到性能。其實主要仍是標識符的解析會影響到性能。而咱們主要是從特別細微的地方去分析做用域的性能問題。 可能有一些地方的優化在引擎不斷優化的狀況下已經成效漸微,可是我以爲仍是有必要去從根源瞭解爲何咱們要這麼作,javascript

追根溯源前端

咱們如下面這個函數爲例java

function add(num1, num2) {
    var sum = num1 + num2;
    return sum;
}
var total = add(5, 10);
複製代碼

咱們先來看一下在執行add函數的時候的做用域鏈 閉包

能夠從圖中看到全局對象位於做用域的末端,活動對象位於做用域的前端

標識符解析: 當函數在執行的時候,每遇到一個變量都會去搜索執行環境的做用域鏈,查找同名的標識符,且搜索過程是從做用域鏈的頭部開始。搜索的時候先以當前運行函數的活動對象開始一直到做用域鏈的最後,若是搜索到則使用這個標識符對應的變量,沒有則爲視爲標識符未定義。 因此一個標識符所在的位置越深,它的讀寫速度越慢,因爲全局變量老是存在於執行環境做用域鏈的最末端。因此函數中局部變量 > 全局變量。函數

other: 當名字相同的兩個變量在做用域鏈的不一樣部分的時候,那麼標識符則爲最早找到的那個性能

var a = 1;
function test(){
    var a = 2;
    return a;
}
test();  // a = 2
複製代碼

最佳實踐優化

  • 用局部變量替換全局變量
  • 減小訪問全局變量或者位於做用域鏈深處的標識符的次數
funtion init(){
    var doc = document,
        bd = doc.body;
        
        doc.getElementById('test').onclick = function(){}
        
        bd.className = '';
}
複製代碼


**觸類旁通**

問:既然做用域對性能有影響,那咱們有什麼辦法去臨時改變做用域鏈麼? 答案是有的,JS中with和try-catch是能夠臨時改變做用域鏈的。ui

疑惑spa

  • with語句主要是爲了將代碼的做用域設置到同一個特定的對象中(額,這句話有點抽象啊!!別急,咱們看一下追根溯源)
  • 爲何都說with語句性能有問題?
  • try-catch又是怎麼改變做用域鏈的,是否有性能問題,又該怎麼避免性能問題?

追根溯源3d

咱們仍是要找個例子,好比:

funtion init(){
    with(document){
        var bd = body,
            links = getElementsByTagName('a');
        
        getElementById('test').onclick = function(){}
        
        bd.className = '';
    }
}
複製代碼

咱們把前面的init函數改造了一下,當執行到with語句的時候,執行環境的做用域鏈被臨時改變了,一個新的活動對象被建立,它包含了參數指定的對象的全部屬性,並將新的活動對象推入做用域鏈的首位(這就解決了咱們的第一點疑惑,因此定義with語句的目的主要是簡化屢次寫同一個對象的工做)。如圖:

能夠從圖中看到,第一位的是參數的對象,第二位是局部變量,第三位是全局變量。 雖然訪問document對象的屬性變快了,可是局部變量的訪問變慢了。這就是with語句的性能問題(咱們的第二點疑惑也解決了)。

try-catch一樣在try代碼塊中發生錯誤的時候,會自動跳轉到catch,其將異常對象推入做用域鏈,並將其置於做用域鏈的首位。同with。

最佳實踐

  • 簡化catch語句的代碼,最好沒有局部變量的訪問
try{
    init()
} catch(error) {
    handleError(error)
}
複製代碼

因爲只有一條語句,且沒有對局部變量的訪問,因此做用域鏈的臨時改動並不會影響性能。



**觸類旁通**

問:函數的閉包也依賴做用域,那閉包有沒有性能問題? 答案:有的

追根溯源

再來個閉包例子:

function assignEvents() {
    var id = 'xdi9592';
    document.getElementById('save-btn').onclick = function(event) {
        saveDocument(id);
    }
}
複製代碼

當執行assignEvents函數時,就會建立包含當前環境的第一個活動對象,而後再就是全局的一個活動對象。當閉包被建立的時候,它的[[scope]]屬性被初始化爲前面的兩個活動對象。以下圖:

一般函數執行完成以後,函數的活動對象會隨着執行環境一塊兒銷燬,可是因爲閉包的引用仍然存在,因此閉包中的活動對象沒法銷燬,所以須要更多的內存消耗,要注意內存泄漏的問題。除了這個以外,當閉包執行的時候,閉包的執行環境還會建立一個當前環境的第一活動對象,以前的兩個引用再日後排,由此變成了下面的樣子:
因爲函數訪問的id和saveDocument不在第一活動對象中,頻繁的跨做用域訪問標識符時,又是一次性能的損耗。

最佳實踐

  • 當心的使用閉包
  • 經常使用的跨做用域鏈的變量存儲在局部變量中,而後訪問局部變量


參考資料:

《高性能JS》

相關文章
相關標籤/搜索