本章,咱們討論一下ECMAScript中的做用域鏈 , 開門見山。前端
什麼是做用域鏈數組
i.ECMAScript是容許建立內部函數的,甚至能從父函數中返回這些函數。做用域鏈正是內部上下文中全部變量對象(及自由對象)的列表。此鏈用來變量解析查詢。瀏覽器
做用域鏈的特性閉包
i.是執行上下文的一個屬性函數
activeExecutionContext = { vo : {}, this : thisValue, scope : [] }
ii.邏輯上是一個數組,每個元素是一個變量對象this
iii.定義爲:Scope = ActiveContext.VO + Function.[[Scope]] ([[Scope]] 是函數的屬性)spa
理解做用域鏈prototype
i.[[Scope]] 是函數的私有屬性,在函數被解析時建立,不會改變。code
爲了讓你們更好的理解,先讓你們看一段代碼:對象
var x = 'test'; function foo() { console.log(x); } (function() { var x = 'what'; foo(); })();
上面的代碼輸出會是什麼呢?爲何?
控制檯將會輸出 'test',而非 'what' 。這個例子也說明,一個函數的[[Scope]] 持續存在,即便是在函數建立的做用域已經完成以後。
ii.[[Scope]] 「一般」(存在乎外) 包含了父級函數的[[Scope]]屬性,ECMAScript依靠這個特性來實現閉包。
iii.[[Scope]]是函數的屬性,這也意味着ECMAScript中沒有Java那樣的塊級做用域。(ES6中對這一塊獲得了增強)只有函數級做用域。
觀察如下3種函數構造方式的差別:
var x = 10; function foo() { var y = 20; // 函數聲明方式建立 function innerFoo() { console.log(x ,y); } //函數表達式方式建立 var innerFoo2 = function() { console.log(x ,y); } //構造函數方式建立 var innerFoo3 = Function('console.log(x);console.log(y);'); innerFoo(); //10 20 innerFoo2(); //10 20 innerFoo3(); //10 ,y not defined }
經過以上代碼,咱們能夠看出,經過Function構造函數建立的方法只擁有全局做用域
iiii.變量的二維鏈式查找
變量的解析是經過做用域鏈來實現的。
變量本質是上以變量對象的屬性方式存在,當變量對象與JavaScript中對象重疊時,它就會自然的受到原型鏈的影響。
一段有趣的代碼:
function foo() { console.log(x); } Object.prototype.x = 10; foo(); //10
緣由: 此時,全局對象爲 window(假設在瀏覽器中運行該段代碼),而window對象是Object所派生的。根據原型鏈查找規則,實例中訪問不到的屬性和方法,將會在原型中查找。
如下面的代碼爲例,其查找順序是這樣的:
iiiii.全局代碼和 eval 的做用域鏈
全局代碼中的做用域鏈僅包含全局對象
eval的上下文與當前 calling context(調用上下文) 擁有相同的做用域鏈
ES5中規定,若是對eval創建別名(非直接調用),這時做用域鏈僅包含全局對象
iiiiii.做用域鏈是能夠在運行時動態改變的
在with 和 catch 語句中:Scope = withObject || catchObject + VO + [[Scope]]
大多數狀況下是不變的,但在with語句和catch語句塊中,能夠改變做用域鏈。這種技巧在有些時候很是有用,但大多數狀況下,咱們要儘量避免
ES5中,經過詞法環境、詞法環境記錄的方式來掃描這種變化
根據咱們上面講到的,你們看看以下代碼:
var x = 10, y = 10; with({x: 20}) { var x = 30 , y = 30; console.log(x ,y); // 30 30 } console.log(x ,y); // 10 30
輸出的結果是: 30 30 10 30 ,爲何呢?實際上上面講到的做用域鏈的動態改變時,已經對該問題作出瞭解答, 下面咱們分析一下:
在with和catch語塊中的做用域鏈:
1.x = 10 ,y = 10;
2.對象{x:20}被添加到了做用域的前端
3.在with內部,遇到了var聲明。可是什麼也沒建立,由於在進入上下文時,全部變量已被解析添加
4.在步驟2中,僅修改變量'x' ,實際上對象中的'x'如今被解析,並添加到做用域鏈的前端,'x'由 20 變爲 30
5.一樣也有變量對象的屬性'y'的修改,被解析後其值也由10變爲30
6.此外,在with聲明完後,它的特定對象從做用域鏈中移除,(已改變的變量「x」--30也從那個對象中移除),即做用域鏈的結構恢復到with獲得增強之前的狀態。
7.最後console中,當前變量對象 'x'保持相同,'y'的值 = 30(在with聲明運行中已發生改變)
iiiiiii.結合this一塊兒
直接調用函數,做用域爲 withObject
var x = 10; with({ foo : function() { console.log(this.x); } , x : 20 }) { foo(); // 20 }
總結
理解執行上下文、VO(變量對象)、this、做用域鏈是理解JavaScript執行的基礎,尤爲是this和做用域鏈