Javascript做用域鏈

對於做用域,能夠分爲全局做用域和局部做用域javascript

全局做用域css

全局做用域中的對象能夠在代碼中的任何地方訪問,通常來講,下面狀況的對象會在全局做用域中:html

  • 最外層函數和在最外層函數外面定義的變量
  • 沒有經過關鍵字"var"聲明的變量
  • 瀏覽器中,window對象的屬性

局部做用域前端

局部做用域又稱函數做用域,全部的變量和函數只能在做用域內部使用。html5

 

var foo = 1; window.bar = 2; function baz(){ a = 3; var b = 4; } // Global scope: foo, bar, baz, a  // Local scope: b

 

做用域鏈java

在Javascript代碼運行中,全部用到的變量都須要去當前變量對象中查找,當找不到的時候,就會繼續查找上層執行環境中的變量對象,這樣一級級向上查找的過程,就是全部執行環境中的變量對象組成了一個做用域鏈。python

因此說,做用域鏈與一個執行上下文相關,是內部上下文全部變量對象(包括父變量對象)的列表,用於變量查詢。android

看一個例子:ios

var x = 10; function foo() { var y = 20; function bar() { var z = 30; console.log(x + y + z); }; bar() }; foo();

上面代碼的輸出結果爲"60",函數bar能夠直接訪問"z",而後經過做用域鏈訪問上層的"x"和"y"。git

綠色箭頭指向變量對象/活動對象

藍色箭頭指向做用域鏈

 再看一個比較典型的例子:

var data = []; for(var i = 0 ; i < 3; i++){ data[i]=function() { console.log(i); } } data[0]();// 3
data[1]();// 3
data[2]();// 3

第一感受(錯覺)這段代碼會輸出」0,1,2″。可是根據前面的介紹,變量」i」是存放在」Global VO」中的變量,循環結束後」i」的值就被設置爲3,因此代碼最後的三次函數調用訪問的是相同的」Global VO」中已經被更新的」i」。

結合做用域鏈看閉包

 在JavaScript中,閉包跟做用域鏈有緊密的關係。相信你們對下面的閉包例子必定很是熟悉,代碼中經過閉包實現了一個簡單的計數器。

function counter() { var x = 0; return { increase: function increase() { return ++x; }, decrease: function decrease() { return --x; } }; } var cter = counter(); console.log(ctor.increase()); console.log(ctor.decrease());

下面咱們就經過Execution Context和scope chain來看看在上面閉包代碼執行中到底作了哪些事情。

1. 當代碼進入Global Context後,會建立全局變量對象

2. 當代碼執行到」var cter = counter();」語句的時候,進入counter Execution Context;根據上一篇文章的介紹,這裏會建立counter AO,並設置counter Execution Context的scope chain

3. 當counter函數執行的最後,並退出的時候,Global VO中的ctor就會被設置;這裏須要注意的是,雖然counter Execution Context退出了執行上下文棧,可是由於ctor中的成員仍然引用counter AO(由於counter AO是increase和decrease函數的parent scope),因此counter AO依然在Scope中。

 

4. 當執行」ctor.increase()」代碼的時候,代碼將進入ctor.increase Execution Context,併爲該執行上下文建立VO/AO,scope chain和設置this;這時,ctor.increase AO將指向counter AO。

 

相信看到這些,必定會對JavaScript閉包有了比較清晰的認識,也瞭解爲何counter Execution Context退出了執行上下文棧,可是counter AO沒有銷燬,能夠繼續訪問。

 

 
 
 
 
 

理解JavaScript的做用域鏈

上一篇文章中介紹了Execution Context中的三個重要部分:VO/AO,scope chain和this,並詳細的介紹了VO/AO在JavaScript代碼執行中的表現。

本文就看看Execution Context中的scope chain。

做用域

開始介紹做用域鏈以前,先看看JavaScript中的做用域(scope)。在不少語言中(C++,C#,Java),做用域都是經過代碼塊(由{}包起來的代碼)來決定的,可是,在JavaScript做用域是跟函數相關的,也能夠說成是function-based。

例如,當for循環這個代碼塊結束後,依然能夠訪問變量」i」。

對於做用域,又能夠分爲全局做用域(Global scope)和局部做用域(Local scpoe)。

全局做用域中的對象能夠在代碼的任何地方訪問,通常來講,下面狀況的對象會在全局做用域中:

  • 最外層函數和在最外層函數外面定義的變量
  • 沒有經過關鍵字」var」聲明的變量
  • 瀏覽器中,window對象的屬性

局部做用域又被稱爲函數做用域(Function scope),全部的變量和函數只能在做用域內部使用。

 

做用域鏈

經過前面一篇文章瞭解到,每個Execution Context中都有一個VO,用來存放變量,函數和參數等信息。

在JavaScript代碼運行中,全部用到的變量都須要去當前AO/VO中查找,當找不到的時候,就會繼續查找上層Execution Context中的AO/VO。這樣一級級向上查找的過程,就是全部Execution Context中的AO/VO組成了一個做用域鏈。

因此說,做用域鏈與一個執行上下文相關,是內部上下文全部變量對象(包括父變量對象)的列表,用於變量查詢。

看一個例子:

上面代碼的輸出結果爲」60″,函數bar能夠直接訪問」z」,而後經過做用域鏈訪問上層的」x」和」y」。

  • 綠色箭頭指向VO/AO
  • 藍色箭頭指向scope chain(VO/AO + All Parent VO/AOs)

再看一個比較典型的例子:

第一感受(錯覺)這段代碼會輸出」0,1,2″。可是根據前面的介紹,變量」i」是存放在」Global VO」中的變量,循環結束後」i」的值就被設置爲3,因此代碼最後的三次函數調用訪問的是相同的」Global VO」中已經被更新的」i」。

結合做用域鏈看閉包

在JavaScript中,閉包跟做用域鏈有緊密的關係。相信你們對下面的閉包例子必定很是熟悉,代碼中經過閉包實現了一個簡單的計數器。

下面咱們就經過Execution Context和scope chain來看看在上面閉包代碼執行中到底作了哪些事情。

1. 當代碼進入Global Context後,會建立Global VO

.

  • 綠色箭頭指向VO/AO
  • 藍色箭頭指向scope chain(VO/AO + All Parent VO/AOs)

 

2. 當代碼執行到」var cter = counter();」語句的時候,進入counter Execution Context;根據上一篇文章的介紹,這裏會建立counter AO,並設置counter Execution Context的scope chain

3. 當counter函數執行的最後,並退出的時候,Global VO中的ctor就會被設置;這裏須要注意的是,雖然counter Execution Context退出了執行上下文棧,可是由於ctor中的成員仍然引用counter AO(由於counter AO是increase和decrease函數的parent scope),因此counter AO依然在Scope中。

4. 當執行」ctor.increase()」代碼的時候,代碼將進入ctor.increase Execution Context,併爲該執行上下文建立VO/AO,scope chain和設置this;這時,ctor.increase AO將指向counter AO。

  • 綠色箭頭指向VO/AO
  • 藍色箭頭指向scope chain(VO/AO + All Parent VO/AOs)
  • 紅色箭頭指向this
  • 黑色箭頭指向parent VO/AO

相信看到這些,必定會對JavaScript閉包有了比較清晰的認識,也瞭解爲何counter Execution Context退出了執行上下文棧,可是counter AO沒有銷燬,能夠繼續訪問。

二維做用域鏈查找

經過上面瞭解到,做用域鏈(scope chain)的主要做用就是用來進行變量查找。可是,在JavaScript中還有原型鏈(prototype chain)的概念。

因爲做用域鏈和原型鏈的相互做用,這樣就造成了一個二維的查找。

對於這個二維查找能夠總結爲:當代碼須要查找一個屬性(property)或者描述符(identifier)的時候,首先會經過做用域鏈(scope chain)來查找相關的對象;一旦對象被找到,就會根據對象的原型鏈(prototype chain)來查找屬性(property)

下面經過一個例子來看看這個二維查找:

var foo = {} function baz() { Object.prototype.a = 'Set foo.a from prototype'; return function inner() { console.log(foo.a); } } baz()(); // Set bar.a from prototype

對於這個例子,能夠經過下圖進行解釋,代碼首先經過做用域鏈(scope chain)查找」foo」,最終在Global context中找到;而後由於」foo」中沒有找到屬性」a」,將繼續沿着原型鏈(prototype chain)查找屬性」a」。

 

相關文章
相關標籤/搜索