JavaScript 函數高級——執行上下文與執行上下文棧(圖解+典型實例分析)
變量提升與函數提升
-
變量聲明提升
- 通過
var
定義(聲明)的變量,在定義語句之前就可以訪問到 - 值:
undefined
- 通過
/*
面試題 : 輸出 undefined
*/
var a = 3
function fn() {
console.log(a) // undefined 調用fn()後進入函數體內部,通過 var 聲明的變量提升,值爲undefined
var a = 4
console.log(a) // 4
}
fn()
console.log('----')
可以用開發工具的代碼調試工具設置斷點,清楚地看到a
的值的變化。
-
函數聲明提升
- 通過
function
聲明的函數,在之前就可以直接調用 - 值:函數定義(對象)
- 通過
console.log(b) // undefined 通過 var 聲明的變量b 變量聲明提升
fn2() // fn2() 通過 function 聲明的函數fn2 函數聲明提升
fn3() // 報錯 通過 var 聲明的函數fn3 變量聲明提升
var b = 3
function fn2() {
console.log('fn2()')
}
var fn3 = function () {
console.log('fn3()')
}
!!注意 var a = 3
, var fn = function(){ }
只要是var
聲明的,都是變量聲明提升,值都是undefined
!!注意:var fn = function(){ }
屬於變量聲明提升,不是函數聲明提升
- 問題:變量提升和函數提升是如何產生的?
執行上下文
// 變量聲明提升,函數聲明提升,全局代碼的函數和方法添加爲Window的方法和屬性
console.log(a1, window.a1) // undefined undefined
window.a2() // a2()
console.log(this) // Window
var a1 = 3
function a2() {
console.log('a2()')
}
console.log(a1) // 3
代碼分類(位置)
- 全局代碼
- 函數代碼
全局執行上下文
- 在執行全局代碼前將
window
確定爲全局執行上下文 -
對全局數據進行預處理:
-
var
定義的全局變量==>undefined
,添加爲window
屬性 -
function
聲明的全局函數==>賦值(fun
),添加爲window
的方法 -
this
==>賦值(window
)
-
- 開始執行全局代碼
函數執行上下文
- 在調用函數,準備執行函數體之前,創建對應的函數執行上下文對象
-
對局部數據進行預處理
- 形參變量==>賦值(實參)==>添加爲執行上下文的屬性
-
arguments
==>賦值(實參列表),添加爲執行上下文的屬性 -
var
定義的局部變量==>undefined
==>添加爲執行上下文的屬性 -
function
聲明的函數==>賦值(fun
)==>添加爲執行上下文的屬性 -
this
==>賦值(調用函數的對象)
- 開始執行函數體代碼
執行上下文棧
理解
- 在全局代碼執行前,
JS
引擎就會創建一個棧來存儲管理所有的執行上下文對象 - 在全局執行上下文(
window
)確定後, 將其添加到棧中(壓棧) - 在函數執行上下文創建後, 將其添加到棧中(壓棧)
- 在當前函數執行完後,將棧頂的對象移除(出棧)
- 當所有的代碼執行完後, 棧中只剩下
window
流程分析
// 1. 進入全局上下文
var a = 10
var bar = function (x) {
var b = 5
// 3. 進入foo執行上下文
foo(x + b)
}
var foo = function (y) {
var c = 5
console.log(a + c + y)
}
// 2. 進入bar函數執行上下文
bar(10) // 30
- 用斷點調試工具查看執行上下文調用棧
面試題
題1:遞歸調用的執行上下文
console.log('gb: ' + i)
var i = 1
foo(1)
function foo(i) {
if (i == 4) {
return
}
console.log('fb:' + i)
foo(i + 1) //遞歸調用: 在函數內部調用自己
console.log('fe:' + i)
}
console.log('ge: ' + i) // ge:1
- 依次輸出什麼?
- 整個過程中產生了幾個執行上下文?
5個
=4個函數執行上下文
+1個全局執行上下文
調試查看:
題2:先執行變量提升,後執行函數提升
function a() { }
var a
console.log(typeof a) // 'function'
題3
if (!(t in window)) {
var t = 1
}
console.log(t) // undefined
t in window
// true
題4(好題)
var c = 1
function c(c) {
console.log(c)
var c = 3
}
c(2) // 報錯 c is not a function
注意:函數提升爲c()
,整個函數提升,所以上面的代碼相當於下面這樣,這樣就很清楚了吧!
var c
function c(c) {
console.log(c)
var c = 3
}
c = 1
c(2) //所以 c is not a function
函數體內的代碼根本就沒機會執行。