根據上篇關於 新手秒懂 - 高逼格解釋變量提高 的文章中說明了,在生成執行上下文的建立階段,生成變量對象後會創建做用域鏈。那咱們接下里就看看做用域和做用域鏈究竟是個啥子玩意。javascript
做用域是一套規則, 用於肯定在何處以及如何查找變量(標識符)。(說白了就是你寫代碼的那塊旮旯裏,來肯定你以後怎麼查找變量,簡單粗暴。。)前端
javascript
採用的是靜態做用域)簡單的例子表述一下:java
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar(); // 1
複製代碼
我將以最簡單的大白話告訴您發生了啥: foo
函數執行 -> 查詢value值(沒有) -> 向上查找(var value = 1
), so, 打印 1
。es6
靜態做用域,只看定義時的位置,就像你跟別人作了鄰居,哪天你老婆吵架了跑出去了,不在家裏。你只要去外面找就好了,別人家就算也有老婆,但確定不是你要找的老婆啊對不對??函數
try/catch
結構在 catch
分句中具備塊做用域。在 ES6 中引入了 let/const
關鍵字( var 關鍵字的表親), 用來在任意代碼塊中聲明變量。 if(..) { let a = 2; }
會聲明一個劫持了 if
的 { .. }
塊的變量,而且將變量添加到這個塊 中。以下例所示:var foo = true;
if (foo) {
let bar = foo * 2;
bar = something( bar );
console.log( bar );
}
console.log( bar ); // ReferenceError
複製代碼
IIFE
之前剛入門的時候被人問到一個問題:請問,當即執行函數表達式的做用是什麼??
post
白癡的我居然把匿名函數和 IIFE(當即執行函數表達式)
認爲是同一個東西。ui
IIFE
: 最多見的用法其實就是使用了匿名函數表達式並最後加入()
,讓它當即執行。var a = 2;
(function () {
var a = 3;
console.log( a ); // 3
// 匿名函數表達式內及是塊級做用域
})();
console.log( a ); // 2
複製代碼
而它的做用主要包括幾點:spa
當查找變量的時候,會先從當前上下文的變量對象中查找,若是沒有找到,就會從父級(詞法層面上的父級)執行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個執行上下文的變量對象構成的鏈表就叫作做用域鏈。code
形象一點就是:對象
做用域鏈就像一棟樓,當前做用域在一樓,全局做用域在頂樓,就是一直往上找你要用的變量。
而編譯器的查找方式有兩種:
// console.log(a) --> VM130:1 Uncaught ReferenceError: a is not defined
ReferenceError
異常(嚴格模式下)。 // a = 1
由於javascript
是靜態做用域,函數的做用域在函數定義的時候就決定了。
這是由於函數有一個內部屬性 [[scope]],當函數建立的時候,就會保存全部父變量對象到其中,你能夠理解 [[scope]] 就是全部父變量對象的層級鏈,可是注意:[[scope]] 並不表明完整的做用域鏈!(意思就是在函數建立時就能夠拿到父級的變量對象VO
)
文字比較難理解不要緊,我們以一個例子說明
function foo() {
function bar() {
...
}
}
複製代碼
在函數建立時, 各自的[[scope]]爲:
foo.[[scope]] = [
globalContext.VO
];
bar.[[scope]] = [
fooContext.AO,
globalContext.VO
]; // 父級對象的 AO/VO(表示變量對象),俺的上篇文章提到過
複製代碼
而以後函數激活, 變量對象就會添加到做用鏈的前端
做用域鏈 = [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, // checkscope上下文
globalContext // 全局上下文
];
複製代碼
3.checkscope 函數啓動(不執行函數內的請求)。開始作準備工做,第一步:複製函數[[scope]]屬性建立做用域鏈
checkscopeContext = {
做用域鏈: checkscope.[[scope]], //上面提到的建立時生成的[[scope]]
}
複製代碼
4.第二步:用 arguments 建立活動對象,隨後初始化活動對象,加入形參、函數聲明、變量聲明(生成變量對象的的幾個過程)
checkscopeContext = {
VO: { // 變量對象
arguments: {
length: 0
},
scope2: undefined
}
}
複製代碼
5.第三步:將活動對象壓入 checkscope 做用域鏈頂端
checkscopeContext = {
VO: {
arguments: {
length: 0
},
scope2: undefined
},
做用域鏈: [VO, [[Scope]]]
}
複製代碼
6.準備工做作完,開始執行函數,隨着函數的執行,修改 AO (活動變量,變量對象的執行階段)的屬性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope' // 函數執行後 scope2 獲取到值
},
做用域鏈: [AO, [[Scope]]]
}
複製代碼
7.查找到 scope2 的值,返回後函數執行完畢,函數上下文從執行上下文棧中彈出
ECStack = [
globalContext
];
複製代碼
這篇主要分享的是做用域相關知識,感受大體瞭解就差很少了,寫的都是我本身的淺薄理解,有錯誤的地方歡迎指出,對於變量對象不瞭解的小夥伴請參照個人上篇文章 新手秒懂 - 高逼格解釋變量提高,仍是一句話,努力,奮鬥💪💪
《你不知道的JavaScript(上卷)》