本期的主題是呼叫堆疊,本計劃一共28期,每期重點攻克一個面試重難點,如果你還不瞭解本進階計劃,文末點選檢視全部文章。
如果覺得本系列不錯,歡迎點贊、評論、轉發,您的支援就是我堅持的最大動力。
JS是單執行緒的語言,執行順序肯定是順序執行,但是JS 引擎並不是一行一行地分析和執行程式,而是一段一段地分析執行,會先進行編譯階段然後才是執行階段。
翠花,上程式碼
例子一:變數提升
foo; // undefined
var foo = function () {
console.log('foo1');
}
foo(); // foo1,foo賦值
var foo = function () {
console.log('foo2');
}
foo(); // foo2,foo重新賦值
複製程式碼
例子二:函式提升
foo(); // foo2
function foo() {
console.log('foo1');
}
foo(); // foo2
function foo() {
console.log('foo2');
}
foo(); // foo2
複製程式碼
例子三:宣告優先順序,函式 > 變數
foo(); // foo2
var foo = function() {
console.log('foo1');
}
foo(); // foo1,foo重新賦值
function foo() {
console.log('foo2');
}
foo(); // foo1
複製程式碼
上面三個例子中,第一個例子是變數提升,第二個例子是函式提升,第三個例子是函式宣告優先順序高於變數宣告。
需要注意的是同一作用域下存在多個同名函式宣告,後面的會替換前面的函式宣告。
執行上下文
執行上下文總共有三種型別
- 全域性執行上下文:只有一個,瀏覽器中的全域性物件就是 window 物件,
this
指向這個全域性物件。 - 函式執行上下文:存在無數個,只有在函式被呼叫的時候才會被建立,每次呼叫函式都會建立一個新的執行上下文。
- Eval 函式執行上下文: 指的是執行在
eval
函式中的程式碼,很少用而且不建議使用。
這部分內容在【進階1-1期】中詳細介紹了,點選檢視【進階1-1期】理解JavaScript 中的執行上下文和執行棧
執行上下文棧
因為JS引擎建立了很多的執行上下文,所以JS引擎建立了執行上下文棧(Execution context stack,ECS)來管理執行上下文。
當 JavaScript 初始化的時候會向執行上下文棧壓入一個全域性執行上下文,我們用 globalContext 表示它,並且只有當整個應用程式結束的時候,執行棧才會被清空,所以程式結束之前, 執行棧最底部永遠有個 globalContext。
ECStack = [ // 使用陣列模擬棧
globalContext
];
複製程式碼
具體執行過程如下圖所示,這部分內容在【進階1-1期】中詳細介紹了,點選檢視【進階1-1期】理解JavaScript 中的執行上下文和執行棧
找不同
有如下兩段程式碼,執行的結果是一樣的,但是兩段程式碼究竟有什麼不同?
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
複製程式碼
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
複製程式碼
答案是 執行上下文棧的變化不一樣。
第一段程式碼:
ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();
ECStack.pop();
複製程式碼
第二段程式碼:
ECStack.push(<checkscope> functionContext);
ECStack.pop();
ECStack.push(<f> functionContext);
ECStack.pop();
複製程式碼
函式上下文
在函式上下文中,用活動物件(activation object, AO)來表示變數物件。
活動物件和變數物件的區別在於
- 1、變數物件(VO)是規範上或者是JS引擎上實現的,並不能在JS環境中直接訪問。
- 2、當進入到一個執行上下文後,這個變數物件才會被啟用,所以叫活動物件(AO),這時候活動物件上的各種屬性才能被訪問。
呼叫函式時,會為其建立一個Arguments物件,並自動初始化區域性變數arguments,指代該Arguments物件。所有作為引數傳入的值都會成為Arguments物件的陣列元素。
執行過程
執行上下文的程式碼會分成兩個階段進行處理
-
1、進入執行上下文
-
2、程式碼執行
進入執行上下文
很明顯,這個時候還沒有執行程式碼
此時的變數物件會包括(如下順序初始化):
- 1、函式的所有形參 (only函式上下文):沒有實參,屬性值設為undefined。
- 2、函式宣告:如果變數物件已經存在相同名稱的屬性,則完全替換這個屬性。
- 3、變數宣告:如果變數名稱跟已經宣告的形參或函式相同,則變數宣告不會干擾已經存在的這類屬性。
上程式碼就直觀了
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1);
複製程式碼
對於上面的程式碼,這個時候的AO是
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
複製程式碼
形參arguments這時候已經有賦值了,但是變數還是undefined,只是初始化的值
程式碼執行
這個階段會順序執行程式碼,修改變數物件的值,執行完成後AO如下
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: 3,
c: reference to function c(){},
d: reference to FunctionExpression "d"
}
複製程式碼
總結如下:
-
1、全域性上下文的變數物件初始化是全域性物件
-
2、函式上下文的變數物件初始化只包括 Arguments 物件
-
3、在進入執行上下文時會給變數物件新增形參、函式宣告、變數宣告等初始的屬性值
-
4、在程式碼執行階段,會再次修改變數物件的屬性值
參考
進階系列目錄
- 【進階1期】 呼叫堆疊
- 【進階2期】 作用域閉包
- 【進階3期】 this全面解析
- 【進階4期】 深淺拷貝原理
- 【進階5期】 原型Prototype
- 【進階6期】 高階函式
- 【進階7期】 事件機制
- 【進階8期】 Event Loop原理
- 【進階9期】 Promise原理
- 【進階10期】Async/Await原理
- 【進階11期】防抖/節流原理
- 【進階12期】模組化詳解
- 【進階13期】ES6重難點
- 【進階14期】計算機網路概述
- 【進階15期】瀏覽器渲染原理
- 【進階16期】webpack配置
- 【進階17期】webpack原理
- 【進階18期】前端監控
- 【進階19期】跨域和安全
- 【進階20期】效能優化
- 【進階21期】VirtualDom原理
- 【進階22期】Diff演算法
- 【進階23期】MVVM雙向繫結
- 【進階24期】Vuex原理
- 【進階25期】Redux原理
- 【進階26期】路由原理
- 【進階27期】VueRouter原始碼解析
- 【進階28期】ReactRouter原始碼解析
交流
進階系列文章彙總:github.com/yygmind/blo…,內有優質前端資料,覺得不錯點個star。
我是木易楊,網易高階前端工程師,跟著我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高階前端的世界,在進階的路上,共勉!