function test(){
console.log(a);//undefined;
var a = 1;
}
test();
也許你會遇到過上面這樣的面試題,你只知道它考的是變量提升,但是具體的原理又知道嗎?所以我覺得很有必要搞明白底層的原理,才能加深理解,其實圍繞的就是
執行上下文
的概念。
什么是執行上下文?
當控制器轉到可執行的代碼時,會進入該代碼對應的執行上下文,可以理解為該代碼對應的一個執行環境,就叫做執行上下文。
在JavaScript中運行環境有三種,分別是:
- 全局環境:JavaScript代碼執行起來,首先就是進入全局環境。
- 函數環境:當函數被調用執行時,就會進入函數中執行。
- eval
所以在一個JavaScript程序中,就會產生多個不同的執行上下文,這時候就需要用到前面提到的棧
數據結構來管理了,我們稱之為調用棧
。當代碼在執行過程中,遇到上面說的三種情況,就會產生三種執行上下文,然后分別壓入調用棧中,等一個執行上下文執行完畢,彈出棧,才能執行下一個執行上下文中的代碼,這就是棧結構的特點。
執行上下文的特點
- 單線程,其實javascript就是單線程,所以很好理解。
- 同步執行,同步就是按順序,不能同時執行。
- 全局上下文只有一個,它在瀏覽器關閉時才會彈出棧。
- 函數的執行上下文的數目沒有限制。
- 每次某個函數被調用時,就會有新的執行上下文,即使是調用的自身函數。
demo01
function f1(){
var n = 999;
function f2(){
alert(n);
}
return f2;
}
var result = f1();
result();//999
我以上面這樣一個例子講解,執行上下文
在調用棧
中的創建過程
image.png
執行上下文的生命周期
image.png
如圖所示,主要分為兩個階段,一個是創建階段
,一個是執行階段
。
創建階段:
- 生成變量對象,后面會講解
- 建立作用域鏈
- 確定this指向
執行階段:
- 變量賦值
- 函數引用
- 執行其他代碼
執行完畢后彈棧,等待回收
變量對象和活動對象的區別就在于,執行周期不一樣,在創建階段叫做變量對象,在執行階段叫做活動對象。
變量對象
image.png
變量對象的創建主要有三個階段:
- 1、創建arguments對象。
- 2、檢查function函數聲明創建屬性。在VO對象中以函數名建立一個屬性,屬性值為函數的地址。如果函數名的屬性已經存在了,那么該屬性將會被新的引用所覆蓋。
- 3、檢查var變量聲明創建屬性。在VO對象中以變量名建立一個屬性,屬性值為
undefined
。為了防止同名的屬性值會被修改為undefined
,則會直接跳過,原屬性值不會被修改。
舉個變量提升和函數提升的例子,就明白了
demo02
function test(){
console.log(a);
console.log(foo());
var a = 1;
function foo(){
return 2;
}
}
test();
這是一個典型的變量提升和函數提升的例子,最后會輸出undefined和2
,接下來以執行上下文的生命周期來講解,
創建過程
testEC = {
VO:{},
scopeChain:{},
this:{}
}
VO = {
arguments:{},
foo:<foo reference>,
a:undefined
}
執行階段
VO->AO
AO={
arguments:{},
foo:<foo reference>,
a:2
}
等同于
function test(){
function foo(){
return 2;
}
var a;
console.log(a);
console.log(foo());
a = 1;
}
test();
通過上面知識的講解,進一步了解到了變量提升和函數提升的底層原理,對后面知識的學習也做了鋪墊。