做用域是每種計算機語言最重要的基礎之一,所以要想深刻的學習JavaScript,做用域和做用域鏈就是個繞不開的話題。前端
在《深刻學習js之—-執行上下文棧》中咱們提到過,當JavaScript代碼執行一段可執行代碼(executable code)時,會建立對應的執行上下文(execution context)。git
對於每一個執行上下文,都有三個重要屬性:github
今天重點聊聊做用域鏈。微信
細說做用域鏈以前,咱們首先來聊聊做用域,簡單的說,做用域就是變量與函數的可訪問範圍,即做用域控制着變量與函數的可見性和生命週期。函數
在JavaScript中,變量的做用域有全局做用域和局部做用域兩種(局部做用域又稱爲函數做用域)。post
代碼在當查找變量的時候,會先從當前上下文的變量對象中查找,若是沒有找到,就會從父級(詞法層面上的父級)執行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。學習
這樣由多個執行上下文的變量對象構成的鏈表就叫作 做用域鏈。
下面,讓咱們以一個函數的建立和激活兩個時期來說解做用域鏈是如何建立和變化的。this
在《深刻學習js之——詞法做用域和動態做用域》中講到,函數的做用域在函數定義的時候就決定了——即JavaScript採用的是靜態做用域。spa
這是由於函數有一個內部屬性 [[scope]]
,當函數建立的時候,就會保存全部父變量對象到其中,你能夠理解 [[scope]]
就是全部父變量對象的層級鏈,可是注意:[[scope]]
並不表明完整的做用域鏈!code
舉個例子:
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 ];
歡迎添加個人我的微信討論技術和個體成長。
歡迎關注個人我的微信公衆號——指尖的宇宙,更多優質思考乾貨