深刻學習js之——做用域鏈#5

開篇

做用域是每種計算機語言最重要的基礎之一,所以要想深刻的學習JavaScript,做用域和做用域鏈就是個繞不開的話題。前端

《深刻學習js之—-執行上下文棧》中咱們提到過,當JavaScript代碼執行一段可執行代碼(executable code)時,會建立對應的執行上下文(execution context)。git

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

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

今天重點聊聊做用域鏈。數組

做用域

細說做用域鏈以前,咱們首先來聊聊做用域,簡單的說,做用域就是變量與函數的可訪問範圍,即做用域控制着變量與函數的可見性和生命週期。微信

在JavaScript中,變量的做用域有全局做用域局部做用域兩種(局部做用域又稱爲函數做用域)。閉包

做用域鏈

代碼在當查找變量的時候,會先從當前上下文的變量對象中查找,若是沒有找到,就會從父級(詞法層面上的父級)執行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。函數

若是從做用域的角度解釋能夠這樣說:post

遍歷做用域鏈的規則很簡單,引擎從當前的執行做用域開始查找變量,若是找不到,就向上一級繼續查找,當抵達最外層的全局做用域時候,不管找到仍是沒有找到,查找過程都中止。學習

這樣由多個執行上下文的變量對象構成的鏈表就叫作做用域鏈this

下面,讓咱們以一個函數的建立和激活兩個時期來說解做用域鏈是如何建立和變化的。

函數建立

《深刻學習js之——詞法做用域和動態做用域》中講到,函數的做用域在函數定義的時候就決定了——即JavaScript採用的是靜態做用域

這是由於函數有一個內部屬性 [[scope]],當函數建立的時候,就會保存全部父變量對象到其中,你能夠理解 [[scope]] 就是全部父變量對象的層級鏈,可是注意:[[scope]] 並不表明完整的做用域鏈!

舉個例子:

function foo(){
  function bar(){
      ...
  }
}
複製代碼

函數建立時,各自的[[scope]]爲:

foo.[[scope]] = [
  globalContext.VO
];

bar.[[scope]] = [
    fooContext.AO,
    globalContext.VO
];
複製代碼

函數激活

當函數激活時,進入函數上下文,建立 VO/AO 後,就會將活動對象添加到做用鏈的前端。

這時候執行上下文的做用域鏈,咱們命名爲 Scope:

Scope = [AO].concat([[Scope]]);
複製代碼

至此,做用域鏈建立完畢。

經過例子深入理解

如下面的例子爲例,結合着以前講的變量對象執行上下文棧,咱們來總結一下函數執行上下文中做用域鏈和變量對象的建立過程:

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

執行過程以下:

1.checkscope 函數被建立,保存做用域鏈到 內部屬性[[scope]]

checkscope.[[scope]] = [
  globalContext.VO
];
複製代碼

2.執行 checkscope 函數,建立 checkscope 函數執行上下文,checkscope 函數執行上下文被壓入執行上下文棧;

ECStack = [
  checkscopeContext,
  globalContext
];
複製代碼

3.checkscope 函數並不馬上執行,開始作準備工做,第一步:複製函數[[scope]]屬性建立做用域鏈;

checkscopeContext = {
    Scope: checkscope.[[scope]],
}
複製代碼

4.第二步:用 arguments 建立活動對象,隨後初始化活動對象,加入形參、函數聲明、變量聲明;

checkscopeContext = {
  AO: {
    arguments: {
      length: 0
    },
    scope2: undefined
  },
  Scope: checkscope.[[scope]],
}
複製代碼

5.第三步:將活動對象壓入 checkscope 做用域鏈頂端;

checkscopeContext = {
  AO: {
    arguments: {
        length: 0
    },
    scope2: undefined
  },
  Scope: [AO, [[Scope]]]
}
複製代碼

6.準備工做作完,開始執行函數,隨着函數的執行,修改 AO 的屬性值;

checkscopeContext = {
  AO: {
    arguments: {
        length: 0
    },
    scope2: 'local scope'
  },
  Scope: [AO, [[Scope]]]
}
複製代碼

7.查找到 scope2 的值,返回後函數執行完畢,函數上下文從執行上下文棧中彈出;

ECStack = [
    globalContext
];
複製代碼

參考:

《JavaScript深刻之做用域鏈》

《深刻了解JavaScript,從做用域鏈開始》

深刻學習JavaScript系列目錄

歡迎添加個人我的微信討論技術和個體成長。

歡迎關注個人我的微信公衆號——指尖的宇宙,更多優質思考乾貨

相關文章
相關標籤/搜索