讓人恍然大悟的詞法做用域及做用域鏈講解

當JavaScript代碼執行一段可執行代碼(executable code)時,會建立對應的執行上下文(execution context)。javascript

對於每一個執行上下文,都有三個重要屬性:html

  • 變量對象(Variable object,VO)
  • 做用域鏈(Scope chain)
  • this

前面已經講解了this,今天來說講做用域及做用域鏈。前端

做用域

做用域是指程序源代碼中定義變量的區域。java

做用域規定了如何查找變量,也就是肯定當前執行代碼對變量的訪問權限。git

JavaScript 採用詞法做用域(lexical scoping),也就是靜態做用域。github

所謂的詞法做用域,就是代碼在編寫過程就體現出來的做用範圍。代碼一旦寫好,不用執行, 做用範圍就已經肯定好了,這個就是所謂的詞法做用域。函數

詞法做用域的規則:post

  1. 函數容許訪問函數外的數據 (也有就近原則)
  2. 整個代碼結構中只有函數能夠限定做用域
  3. 做用域內首先使用變量提高分析
  4. 若是當前做用域找到所需變量,則中止查找

*與詞法做用域相對的是動態做用域,函數的做用域是在函數調用的時候才決定的。ui

var value = 1;

function foo() {
    console.log(value);
}

function bar() {
    var value = 2;
    foo();
}

bar(); // 1
複製代碼

分析下執行過程:this

執行 foo 函數,先從 foo 函數內部查找是否有局部變量 value,若是沒有,就根據書寫的位置,查找上面一層的代碼,也就是 value 等於 1,因此結果會打印 1。

假設JavaScript採用動態做用域,讓咱們分析下執行過程:

執行 foo 函數,依然是從 foo 函數內部查找是否有局部變量 value。若是沒有,就從調用函數的做用域,也就是 bar 函數內部查找 value 變量,因此結果會打印 2。

看一個《JavaScript權威指南》中的例子:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();
複製代碼
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();
複製代碼

兩段代碼都會打印:local scope。由於JavaScript採用的是詞法做用域,函數的做用域基於函數建立的位置。

引用《JavaScript權威指南》的回答就是:

JavaScript 函數的執行用到了做用域鏈,這個做用域鏈是在函數定義的時候建立的。嵌套的函數 f() 定義在這個做用域鏈裏,其中的變量 scope 必定是局部變量,無論什麼時候何地執行函數 f(),這種綁定在執行 f() 時依然有效。

做用域鏈

當查找變量的時候,會先從當前上下文的變量對象中查找,若是沒有找到,就會從父級(詞法層面上的父級)執行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個執行上下文的變量對象構成的鏈表就叫作做用域鏈。

這裏不具體介紹函數的[[scope]]屬性,只介紹一下怎麼簡單的分析做用域鏈。

做用域鏈繪製規則以下:

  1. 全局變量,函數聲明都是屬於0級鏈,每一個對象佔一個位置
  2. 遇到函數聲明就延伸一個鏈出來,一級級展開
  3. 訪問變量時首先從當前函數內部查找,若是當前做用域沒有定義,往上級鏈中檢查
  4. 如此往復,直到0級鏈,若是0級沒有,則這個變量爲undefined

以代碼爲例:

function func1(){
   alert(num);  
}

function func2(){
   var num=456;
   function func3(){
       func1();
   }
   func3();  
}

func2();//結果顯示:num is not defined
複製代碼

上述代碼做用域鏈以下:

分析下代碼執行流程:

  1. 當程序執行到func2時,進入func2的執行環境
  2. 當程序執行到func3時,在func3上下文中沒有找到func1
  3. 回到1級做用域,即func3聲明位置所在的1級做用域尋找,沒有找到
  4. 回到func2聲明時所在0級做用域尋找,找到後進入func1的上下文環境
  5. func1歸屬於0級做用域,內部只能訪問func1內部做用域及0級做用域,在func1做用域內沒有找到num,回到0級做用域,也沒有找到,拋出錯誤

請注意:

  1. 只有函數聲明會延伸下級鏈,調用只會查找,不會展開下級鏈
  2. 全部鏈都能訪問到0級鏈,0級鏈只能訪問0級鏈

相關係列: 從零開始的前端築基之旅(超級精細,持續更新~)

若是你收穫了新知識,就給做者點個贊吧~他急需支持~

參考文章:

  1. ****JavaScript深刻之詞法做用域和動態做用域****
  2. ****JavaScript深刻之做用域鏈****
  3. ****JS-詞法做用域 做用域鏈****
相關文章
相關標籤/搜索