
對于ES3每個執(zhí)行上下文,都有三個重要屬性:
- 變量對象(Variable object,VO)
- 作用域鏈(Scope chain)
- this
這篇我們來聊聊這三個重要屬性
變量對象
變量對象作為執(zhí)行上下文的一種屬性,每次創(chuàng)建后,根據(jù)執(zhí)行環(huán)境不同上下文下的變量對象也稍有不同,我們比較熟悉的就是全局對象和函數(shù)對象,所以我們來聊聊全局上下文下的變量對象和函數(shù)上下文下的變量對象。
全局上下文
我們先了解一個概念,什么叫全局對象。在 W3School 中:
全局對象是預(yù)定義的對象,作為 JavaScript 的全局函數(shù)和全局屬性的占位符。通過使用全局對象,可以訪問所有其他所有預(yù)定義的對象、函數(shù)和屬性。
在頂層 JavaScript 代碼中,可以用關(guān)鍵字 this 引用全局對象。因為全局對象是作用域鏈的頭,這意味著所有非限定性的變量和函數(shù)名都會作為該對象的屬性來查詢。
例如,當(dāng)JavaScript 代碼引用 parseInt() 函數(shù)時,它引用的是全局對象的 parseInt 屬性。全局對象是作用域鏈的頭,還意味著在頂層 JavaScript 代碼中聲明的所有變量都將成為全局對象的屬性。
我們可以根據(jù)代碼理解
- 可以通過 this 引用,在客戶端 JavaScript 中,全局對象就是 Window 對象。
console.log(this); //window
- 全局對象是由 Object 構(gòu)造函數(shù)實例化的一個對象。
console.log(this instanceof Object);//true
- 我們調(diào)用的一些方法都在window下。
console.log(Math.random());
console.log(this.Math.random());
4.作為全局變量的宿主。
var a = 1;
console.log(this.a);
5.客戶端 JavaScript 中,全局對象有 window 屬性指向自身。
var a = 1;
console.log(window.a);//1
this.window.b = 2;
console.log(this.b);//2
我們發(fā)現(xiàn)全局上下文中的變量對象就是全局對象
函數(shù)上下文
在函數(shù)上下文中,不同于全局上下文比較死板,我們用活動對象(activation object, AO)來表示變量對象。
所以活動對象和變量對象其實是一個東西,只是變量對象是規(guī)范上或者說是引擎實現(xiàn)上不可在 JavaScript 環(huán)境中直接訪問,只有到當(dāng)進入一個執(zhí)行上下文中,這個執(zhí)行上下文的變量對象才會被激活,所以稱為activation object,只有在激活狀態(tài)才會對屬性進行訪問。
活動對象是在進入函數(shù)上下文時刻被創(chuàng)建的,它通過函數(shù)的 arguments屬性初始化。arguments 屬性值是 Arguments 對象。
執(zhí)行過程
執(zhí)行上下文的代碼會分成兩個階段進行處理:分析和執(zhí)行,我們也可以叫做:
- 進入執(zhí)行上下文
- 代碼執(zhí)行
進入執(zhí)行上下文
當(dāng)進入執(zhí)行上下文時,這時候還沒有執(zhí)行代碼,
變量對象會包括:
-
函數(shù)的所有形參 (如果是函數(shù)上下文)
- 由名稱和對應(yīng)值組成的一個變量對象的屬性被創(chuàng)建
- 沒有實參,屬性值設(shè)為 undefined
-
函數(shù)聲明
- 由名稱和對應(yīng)值(函數(shù)對象(function-object))組成一個變量對象的屬性被創(chuàng)建
- 如果變量對象已經(jīng)存在相同名稱的屬性,則完全替換這個屬性
-
變量聲明
- 由名稱和對應(yīng)值(undefined)組成一個變量對象的屬性被創(chuàng)建
- 如果變量名稱跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會干擾已經(jīng)存在的這類屬性
舉個例子:
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b =3;
}
foo(1);
在進入執(zhí)行上下文后,這時候的 AO 是:
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
代碼執(zhí)行
在代碼執(zhí)行階段,會順序執(zhí)行代碼,根據(jù)代碼,修改變量對象的值
還是上面的例子,當(dāng)代碼執(zhí)行完后,這時候的 AO 是:
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: 3,
c: reference to function c(){},
d: reference to FunctionExpression "d"
}
到這里變量對象的創(chuàng)建過程就介紹完了,讓我們簡潔的總結(jié)我們上述所說:
- 全局上下文的變量對象初始化是全局對象
- 函數(shù)上下文的變量對象初始化只包括 Arguments 對象
- 在進入執(zhí)行上下文時會給變量對象添加形參、函數(shù)聲明、變量聲明等初始的屬性值
- 在代碼執(zhí)行階段,會再次修改變量對象的屬性值
例子
function foo() {
console.log(a);
a = 1;
}
foo(); // ???
function bar() {
a = 1;
console.log(a);
}
bar(); // ???
第一段會報錯:Uncaught ReferenceError: a is not defined。
第二段會打?。?code>1。
這是因為函數(shù)中的 "a" 并沒有通過 var 關(guān)鍵字聲明,所有不會被存放在 AO 中。
第一段執(zhí)行 console 的時候, AO 的值是:
AO = {
arguments: {
length: 0
}
}
沒有 a 的值,然后就會到全局去找,全局也沒有,所以會報錯。
當(dāng)?shù)诙螆?zhí)行 console 的時候,全局對象已經(jīng)被賦予了 a 屬性,這時候就可以從全局找到 a 的值,所以會打印 1。
但是這個例子在非嚴格模式下才會成立,因為嚴格模式并不會主動幫你創(chuàng)建一個變量
再看看另一個例子
console.log(foo);
function foo(){
console.log("foo");
}
var foo = 1;
會打印函數(shù),而不是 undefined 。
這是因為在進入執(zhí)行上下文時,首先會處理函數(shù)聲明,其次會處理變量聲明,如果如果變量名稱跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會干擾已經(jīng)存在的這類屬性。
作用域
在講解作用域鏈之前,先說說作用域
作用域是指程序源代碼中定義變量的區(qū)域。
作用域?qū)θ绾尾檎易兞窟M行了規(guī)定,也就是確定當(dāng)前執(zhí)行代碼對變量的訪問權(quán)限。
JavaScript 采用詞法作用域(lexical scoping),也就是靜態(tài)作用域。
編譯原理
我們都知道JavaScript是一門動態(tài)語言或是解釋性語言,但事實上它是一門編譯語言。
程序中一段源碼在執(zhí)行前虎易經(jīng)理三個步驟,統(tǒng)稱為“編譯”
- 分詞/詞法分析(Tokenizing/Lexing)
這個過程會將由字符組成的字符串分解成有意義的代碼塊,這些代碼塊被稱為詞法單元,例如:var = 2;。這段代碼會分解成var、a、=、2、;。如果詞法單元生成器在判斷a是一個獨立的分詞單元還是其他詞法單元的一部分時,調(diào)用的是有狀態(tài)的解析規(guī)則,那么這個過程就稱為詞法分析。
- 解析/語法分析(Parsing)
這個過程是將詞法單元流動(數(shù)組)轉(zhuǎn)漢城一個由元素所組成的代表了程序語法結(jié)構(gòu)的書。
這個書稱為“抽象語法樹(AST)”,var a = 2;的抽象語法樹,可能會有一個叫做VariableDeclearation的頂級節(jié)點,接下來是一個叫作Identifier(它的值是 a)的子節(jié)點,以及一個叫作AssignmentExpresstion的子節(jié)點,AssignmentExpresstion節(jié)點有一個叫作NumericLiteral(它的值是2)的子節(jié)點。
- 代碼生產(chǎn)
將AST轉(zhuǎn)換為可執(zhí)行代碼的過程為代碼生成
簡單來說,就是有某種方法將var a = 2; 的AST轉(zhuǎn)換為一組機器指令,用來創(chuàng)建一個叫作a的變量(包括分配內(nèi)存),并將一個值儲存在a中。
賦值操作
JavaScript在引擎中,變量的賦值操作會執(zhí)行兩個動作,首先編譯器會在當(dāng)前作用域中聲明一個變量(如果之前沒聲明過),然后在運行時引擎會在作用域中查找該變量,如果能夠找到就會給它賦值
在編譯器中的過程
先引入兩個名詞
RHS:負責(zé)查找某個變量的值
LHS:找到變量的容器本身,從而對其賦值
現(xiàn)在我們以console.log(a)為例,其中對a的引用進行是一個RHS引用,因為這里a并沒有賦予任何值。響應(yīng)地,需要查找并取得a的值,這樣值就傳遞給console.log()。
相比之下,例如:
a = 2;
這里對a的引用則是LHS的引用,因為實際上我們并不關(guān)心當(dāng)前的值是什么,只是想為= 2這個值操作找個一個目標或是容器
一個例子:
function foo(a){
console.log(a + b)
}
var b = 2
foo(2)
首先會對b進行RHS查詢,無法在函數(shù)內(nèi)部獲得值,就會在上一級作用域查找,找到b之后再進行RHS查詢。就是說,如果該變量如果在該作用域沒有找到對應(yīng)的賦值,就會向上查找,直到找到對應(yīng)的賦值。
靜態(tài)作用域與動態(tài)作用域
我們大多使用的作用域是詞法作用域, 而函數(shù)的作用域在函數(shù)定義的時候就決定了。
而與詞法作用域相對的是動態(tài)作用域,函數(shù)的作用域是在函數(shù)調(diào)用的時候才決定的。
讓我們認真看個例子就能明白之間的區(qū)別:
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
假設(shè)JavaScript采用靜態(tài)作用域,讓我們分析下執(zhí)行過程:
執(zhí)行 foo 函數(shù),先從 foo 函數(shù)內(nèi)部查找是否有局部變量 value,如果沒有,就根據(jù)書寫的位置,查找上面一層的代碼,也就是 value 等于 1,所以結(jié)果會打印 1。
假設(shè)JavaScript采用動態(tài)作用域,讓我們分析下執(zhí)行過程:
執(zhí)行 foo 函數(shù),依然是從 foo 函數(shù)內(nèi)部查找是否有局部變量 value。如果沒有,就從調(diào)用函數(shù)的作用域,也就是 bar 函數(shù)內(nèi)部查找 value 變量,所以結(jié)果會打印 2。
前面我們已經(jīng)說了,JavaScript采用的是靜態(tài)作用域,所以這個例子的結(jié)果是 1。
動態(tài)作用域
bash 就是動態(tài)作用域
例如:
value=1
function foo () {
echo $value;
}
function bar () {
local value=2;
foo;
}
bar
作用域鏈
說完了作用域,終于到作用域鏈了。當(dāng)查找變量的時候,會先從當(dāng)前上下文的變量對象中查找,如果沒有找到,就會從父級(詞法層面上的父級)執(zhí)行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個執(zhí)行上下文的變量對象構(gòu)成的鏈表就叫做作用域鏈。
下面,讓我們以一個函數(shù)的創(chuàng)建和激活兩個時期來講解作用域鏈是如何創(chuàng)建和變化的。
函數(shù)創(chuàng)建
函數(shù)的作用域在函數(shù)定義的時候就決定了。
這是因為函數(shù)有一個內(nèi)部屬性 [[scope]],當(dāng)函數(shù)創(chuàng)建的時候,就會保存所有父變量對象到其中,你可以理解 [[scope]] 就是所有父變量對象的層級鏈,但是需要注意:[[scope]] 并不代表完整的作用域鏈
舉個例子:
function foo() {
function bar() {
...
}
}
函數(shù)創(chuàng)建時,各自的[[scope]]為:
foo.[[scope]] = [
globalContext.VO
];
bar.[[scope]] = [
fooContext.AO,
globalContext.VO
];
函數(shù)激活
當(dāng)函數(shù)激活時,進入函數(shù)上下文,創(chuàng)建 VO/AO后,就會將活動對象添加到作用鏈的前端。
這時候執(zhí)行上下文的作用域鏈,我們命名為Scope:
Scope = [AO].concat([[Scope]]);
這樣我們就創(chuàng)建了一個作用域鏈。
重新思考
以下面的例子為例,結(jié)合著之前講的變量對象和執(zhí)行上下文棧,我們來總結(jié)一下函數(shù)執(zhí)行上下文中作用域鏈和變量對象的創(chuàng)建過程:
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
執(zhí)行過程如下:
- checkscope 函數(shù)被創(chuàng)建,保存作用域鏈到內(nèi)部屬性[[scope]]
checkscope.[[scope]] = [
globalContext.VO
];
- 執(zhí)行 checkscope 函數(shù),創(chuàng)建 checkscope 函數(shù)執(zhí)行上下文,checkscope 函數(shù)執(zhí)行上下文被壓入執(zhí)行上下文棧
ECStack = [
checkscopeContext,
globalContext
];
- checkscope 函數(shù)并不立刻執(zhí)行,開始做準備工作,第一步:復(fù)制函數(shù)[[scope]]屬性創(chuàng)建作用域鏈
checkscopeContext = {
Scope: checkscope.[[scope]],
}
- 第二步:用 arguments 創(chuàng)建活動對象,隨后初始化活動對象,加入形參、函數(shù)聲明、變量聲明
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]],
}
- 第三步:將活動對象壓入 checkscope 作用域鏈頂端
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
- 準備工作做完,開始執(zhí)行函數(shù),隨著函數(shù)的執(zhí)行,修改 AO 的屬性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
- 查找到 scope2 的值,返回后函數(shù)執(zhí)行完畢,函數(shù)上下文從執(zhí)行上下文棧中彈出
ECStack = [
globalContext
];
this
好吧,現(xiàn)在在說說this的問題,總結(jié)性的東西,面試題都會刷到,我就不多說了,下面我講講面試不考的知識,說說this到底是什么
先看一段代碼
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo();
聰明的同學(xué)肯定會發(fā)現(xiàn)會發(fā)現(xiàn)結(jié)果是undefined,在嚴格模式下會報錯,首先,這段代碼試圖通過 this.bar() 來引用 bar() 函數(shù)。但是調(diào)用 bar() 最自然的方法是省略前面的 this,直接使用詞法引用標識符。
此外,我們發(fā)現(xiàn)我們試圖通過內(nèi)部調(diào)用函數(shù)來改變詞法作用域,從而讓bar() 可以訪問 foo() 作用域里的變量 a。這是不可能實現(xiàn)的。this 是在運行時進行綁定的,并不是在編寫時綁定,它的上下文取決于函數(shù)調(diào)用時的各種條件。this 的綁定和函數(shù)聲明的位置沒有任何關(guān)系,只取決于函數(shù)的調(diào)用方式。
當(dāng)一個函數(shù)被調(diào)用時,會創(chuàng)建一個活動對象。這個對象會包含函數(shù)在哪里被調(diào)用、函數(shù)的調(diào)用方法、傳入的參數(shù)等信息。this 就是記錄的其中一個屬性,會在函數(shù)執(zhí)行的過程中用到。也就是說this在函數(shù)創(chuàng)建的時候,已經(jīng)形成了。
這樣執(zhí)行上下文的三個屬性就講完了,大概過程如圖所示:
回顧
上面我們把三大屬性就講解了一遍,下面說說以前做過的例子:
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
兩段代碼都會打印'local scope'。雖然兩段代碼執(zhí)行的結(jié)果一樣,但是兩段代碼究竟有哪些不同呢?
具體執(zhí)行分析
我們分析第一段代碼:
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
執(zhí)行過程如下:
- 執(zhí)行全局代碼,創(chuàng)建全局執(zhí)行上下文,全局上下文被壓入執(zhí)行上下文棧
ECStack = [
globalContext
];
- 全局上下文初始化
globalContext = {
VO: [global],
Scope: [globalContext.VO],
this: globalContext.VO
}
- 初始化的同時,checkscope 函數(shù)被創(chuàng)建,保存作用域鏈到函數(shù)的內(nèi)部屬性[[scope]]
checkscope.[[scope]] = [
globalContext.VO
];
- 執(zhí)行 checkscope 函數(shù),創(chuàng)建 checkscope 函數(shù)執(zhí)行上下文,checkscope 函數(shù)執(zhí)行上下文被壓入執(zhí)行上下文棧
ECStack = [
checkscopeContext,
globalContext
];
- checkscope 函數(shù)執(zhí)行上下文初始化:
- 復(fù)制函數(shù) [[scope]] 屬性創(chuàng)建作用域鏈,
- 用 arguments 創(chuàng)建活動對象,
- 初始化活動對象,即加入形參、函數(shù)聲明、變量聲明,
- 將活動對象壓入 checkscope 作用域鏈頂端。
同時 f 函數(shù)被創(chuàng)建,保存作用域鏈到 f 函數(shù)的內(nèi)部屬性[[scope]]
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope: undefined,
f: reference to function f(){}
},
Scope: [AO, globalContext.VO],
this: undefined
}
- 執(zhí)行 f 函數(shù),創(chuàng)建 f 函數(shù)執(zhí)行上下文,f 函數(shù)執(zhí)行上下文被壓入執(zhí)行上下文棧
ECStack = [
fContext,
checkscopeContext,
globalContext
];
- f 函數(shù)執(zhí)行上下文初始化, 以下跟第 4 步相同:
- 復(fù)制函數(shù) [[scope]] 屬性創(chuàng)建作用域鏈
- 用 arguments 創(chuàng)建活動對象
- 初始化活動對象,即加入形參、函數(shù)聲明、變量聲明
- 將活動對象壓入 f 作用域鏈頂端
fContext = {
AO: {
arguments: {
length: 0
}
},
Scope: [AO, checkscopeContext.AO, globalContext.VO],
this: undefined
}
f 函數(shù)執(zhí)行,沿著作用域鏈查找 scope 值,返回 scope 值
f 函數(shù)執(zhí)行完畢,f 函數(shù)上下文從執(zhí)行上下文棧中彈出
ECStack = [
checkscopeContext,
globalContext
];
- checkscope 函數(shù)執(zhí)行完畢,checkscope 執(zhí)行上下文從執(zhí)行上下文棧中彈出
ECStack = [
globalContext
];
ES5標準
ES5中在 我們改進了命名方式
- 詞法環(huán)境(lexical environment)
- 變量環(huán)境(variable environment)
- this (this value)
所以執(zhí)行上下文在概念上表示如下:
ExecutionContext = {
ThisBinding = <this value>,
LexicalEnvironment = { ... },
VariableEnvironment = { ... },
}
詞法環(huán)境
官方的 ES5 文檔把詞法環(huán)境定義為
詞法環(huán)境是一種規(guī)范類型,基于 ECMAScript 代碼的詞法嵌套結(jié)構(gòu)來定義標識符和具體變量和函數(shù)的關(guān)聯(lián)。一個詞法環(huán)境由環(huán)境記錄器和一個可能的引用外部詞法環(huán)境的空值組成。
簡單來說詞法環(huán)境是一種持有標識符—變量映射的結(jié)構(gòu)。(這里的標識符指的是變量/函數(shù)的名字,而變量是對實際對象[包含函數(shù)類型對象]或原始數(shù)據(jù)的引用)。
現(xiàn)在,在詞法環(huán)境的內(nèi)部有兩個組件:(1) 環(huán)境記錄器和 (2) 一個外部環(huán)境的引用。
環(huán)境記錄器是存儲變量和函數(shù)聲明的實際位置。
外部環(huán)境的引用意味著它可以訪問其父級詞法環(huán)境(作用域)。
詞法環(huán)境有兩種類型:
全局環(huán)境(在全局執(zhí)行上下文中)是沒有外部環(huán)境引用的詞法環(huán)境。全局環(huán)境的外部環(huán)境引用是 null。它擁有內(nèi)建的 Object/Array/等、在環(huán)境記錄器內(nèi)的原型函數(shù)(關(guān)聯(lián)全局對象,比如 window 對象)還有任何用戶定義的全局變量,并且 this的值指向全局對象。
在函數(shù)環(huán)境中,函數(shù)內(nèi)部用戶定義的變量存儲在環(huán)境記錄器中。并且引用的外部環(huán)境可能是全局環(huán)境,或者任何包含此內(nèi)部函數(shù)的外部函數(shù)。
環(huán)境記錄器也有兩種類型:
聲明式環(huán)境記錄器存儲變量、函數(shù)和參數(shù)。
對象環(huán)境記錄器用來定義出現(xiàn)在全局上下文中的變量和函數(shù)的關(guān)系。
簡而言之,
在全局環(huán)境中,環(huán)境記錄器是對象環(huán)境記錄器。
在函數(shù)環(huán)境中,環(huán)境記錄器是聲明式環(huán)境記錄器。
對于函數(shù)環(huán)境,聲明式環(huán)境記錄器還包含了一個傳遞給函數(shù)的 arguments 對象(此對象存儲索引和參數(shù)的映射)和傳遞給函數(shù)的參數(shù)的 length。
抽象地講,詞法環(huán)境在偽代碼中看起來像這樣:
GlobalExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 在這里綁定標識符
}
outer: <null>
}
}
FunctionExectionContext = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 在這里綁定標識符
}
outer: <Global or outer function environment reference>
}
}
變量環(huán)境
它同樣是一個詞法環(huán)境,其環(huán)境記錄器持有變量聲明語句在執(zhí)行上下文中創(chuàng)建的綁定關(guān)系。
如上所述,變量環(huán)境也是一個詞法環(huán)境,所以它有著上面定義的詞法環(huán)境的所有屬性。
在 ES6 中,詞法環(huán)境組件和變量環(huán)境的一個不同就是前者被用來存儲函數(shù)聲明和變量(let 和 const)綁定,而后者只用來存儲 var 變量綁定。
我們看點樣例代碼來理解上面的概念:
let a = 20;
const b = 30;
var c;
function multiply(e, f) {
var g = 20;
return e * f * g;
}
c = multiply(20, 30);
執(zhí)行上下文看起來像這樣:
GlobalExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 在這里綁定標識符
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
outer: <null>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 在這里綁定標識符
c: undefined,
}
outer: <null>
}
}
FunctionExectionContext = {
ThisBinding: <Global Object>,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 在這里綁定標識符
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 在這里綁定標識符
g: undefined
},
outer: <GlobalLexicalEnvironment>
}
}
只有遇到調(diào)用函數(shù) multiply 時,函數(shù)執(zhí)行上下文才會被創(chuàng)建。
可能你已經(jīng)注意到 let 和 const 定義的變量并沒有關(guān)聯(lián)任何值,但 var 定義的變量被設(shè)成了 undefined。
這是因為在創(chuàng)建階段時,引擎檢查代碼找出變量和函數(shù)聲明,雖然函數(shù)聲明完全存儲在環(huán)境中,但是變量最初設(shè)置為 undefined(var 情況下),或者未初始化(let 和 const 情況下)。
這就是為什么你可以在聲明之前訪問 var 定義的變量(雖然是 undefined),但是在聲明之前訪問 let 和 const 的變量會得到一個引用錯誤。
這就是我們說的變量聲明提升。
執(zhí)行階段
這是整篇文章中最簡單的部分。在此階段,完成對所有這些變量的分配,最后執(zhí)行代碼。
注意 — 在執(zhí)行階段,如果 JavaScript 引擎不能在源碼中聲明的實際位置找到 let 變量的值,它會被賦值為 undefined。
總結(jié)
本篇文章對執(zhí)行上下文進行了深入的討論,也對不同的標準進行了大致的分析,意義在于略懂一些底層知識。說了那么多也寫不好代碼,知道個大概就好了。
JavaScript基礎(chǔ)專題系列
JavaScript基礎(chǔ)系列目錄地址:
JavaScript基礎(chǔ)專題之執(zhí)行上下文和執(zhí)行棧(二)
新手寫作,如果有錯誤或者不嚴謹?shù)牡胤?,請大伙給予指正。如果這片文章對你有所幫助或者有所啟發(fā),還請給一個贊,鼓勵一下作者,在此謝過。