0、自己理解
程式碼執行或函式呼叫生成執行上下文(只有當前執行上下文有執行權),該執行上下文內只能訪問當前執行上下文的變數、函式和上一級執行上下文中的變數、函式,啟用下一個執行上下文的時候執行權轉移到新的執行上下文,形成執行上下文棧。
作用域是當前執行上下文中能訪問的變數、函式的集合,執行上下文中只能訪問當前作用域和其上執行上下文的作用域,由此形成作用域鏈
1、執行上下文(棧)
每一次程式碼執行和函式呼叫都會產生一個執行環境,稱為執行上下文(context stack)。
一個執行上下文caller
又可以啟用(呼叫)另一個執行上下文callee
,這時caller
會暫停自身的執行把控制權交給callee
進入callee
的執行上下文,callee
執行完畢後將控制權交回caller
,callee
可以用return
或者丟擲Exception
來結束自己的執行。
多個執行上下文會形成執行上下文棧,最頂層是當前執行上下文,底層是全域性執行上下文。
2、作用域(鏈)
作用域(scope chain)是每一個執行上下文自身持有的活動物件的集合,如在本執行上下文中宣告的變數和函式以及方法引數傳入的物件。
每一個執行上下文可以訪問的物件包括自身的作用域和父執行上下文的作用域和父父執行上下文作用域直到全域性作用域,這就產生了作用域鏈。作用域鏈的用途是保證對執行環境有權訪問的所有變數和函式的有序訪問。
作用域鏈的工作原理跟原型鏈十分相似:如果本身的作用域中查詢不到識別符號,那麼就查詢父作用域,直到頂層。
目前假設作用域的聯動是用的__parent__物件,它指向作用域鏈的下一個物件。(在ES5中,確實有一個outer連結)
全域性上下文的作用域包含Object.prototype中的物件,with和catch會改變作用域鏈,在with中,查詢__parent__之前會先去查詢__proto__,會使作用域鏈增大。
2.1 作用域鏈中的名稱解析順序
javascript中一個名字(name)以四種方式進入作用域(scope),其優先順序順序如下:
- 語言內建:所有的作用域中都有 this 和 arguments 關鍵字
- 形式引數:函式的引數在函式作用域中都是有效的
- 函式宣告:形如function foo() {}
- 變數宣告:形如var bar;
名字宣告的優先順序如上所示,也就是說如果一個變數的名字與函式的名字相同,那麼函式的名字會覆蓋變數的名字,無論其在程式碼中的順序如何;形式引數會覆蓋變數宣告。但名字的初始化卻是按其在程式碼中書寫的順序進行的,不受以上優先順序的影響。
(function(){
var foo;
console.log(typeof foo); //function
function foo(){}
foo = "foo";
console.log(typeof foo); //string
})();
如果形參中有多個同名變數,那麼最後一個同名引數會覆蓋其他同名引數,即使最後一個同名引數未定義;以上的名字解析優先順序存在例外,比如可以覆蓋語言內建的名字arguments。
2.2 with改變作用域鏈
with語句主要用來臨時擴充套件作用域鏈,將語句中的物件新增到作用域的頭部。
person={name:"yhb",age:22,height:175,wife:{name:"lwy",age:21}};
with(person.wife){
console.log(name);
}
with語句將person.wife
新增到當前作用域鏈的頭部,所以輸出的就是lwy
。with語句結束後,作用域鏈恢復正常。
3、二維作用域鏈查詢
源於ECMAScript的原型特性。如果一個屬性在物件中沒有直接找到,查詢將在原型鏈中繼續。即常說的二維鏈查詢。
- 作用域鏈環節;
- 每個作用域鏈-深入到原型鏈環節
網上的帖子大多深淺不一,甚至有些前後矛盾,在下的文章都是學習過程中的總結,如果發現錯誤,歡迎留言指出~
參考:
1、執行上下文(棧)/作用域(鏈)/with
2、Js 作用域與作用域鏈與執行上下文不得不說的故事