執(zhí)行上下文的概念
執(zhí)行上下文:javascript 代碼解析和執(zhí)行時所在的環(huán)境。
執(zhí)行上下文的類型
執(zhí)行上下文分為三種類型:
1.全局執(zhí)行上下文
- js代碼開始運行后睬塌。首先進入全局執(zhí)行上下文環(huán)境中,不在任何函數(shù)中的js代碼都會在全局執(zhí)行上下穩(wěn)重
- 一個js程序中只存在一個全局執(zhí)行上下文勋陪。創(chuàng)建時會壓人棧底硫兰,只有當程序結(jié)束時才會彈出
- 全局執(zhí)行上下文會做兩件事。1.創(chuàng)建全局對象瞄崇,2.將this指向這個全局對象
- 瀏覽器環(huán)境中全局對象是window, 在node環(huán)境中全局對象是global
2.函數(shù)執(zhí)行上下文
- 函數(shù)每次調(diào)用都會產(chǎn)生一個新的函數(shù)執(zhí)行上下文,每個函數(shù)都擁有自己的執(zhí)行上下文苏研,但是只有調(diào)用的時候才會被創(chuàng)建
- 函數(shù)執(zhí)行上下文的生命周期分為兩個階段。創(chuàng)建和執(zhí)行
3.Eval執(zhí)行上下文
- eval函數(shù)執(zhí)行時產(chǎn)生的執(zhí)行上下文筹燕。
執(zhí)行上下文棧
執(zhí)行上下文棧是一個后進先出的數(shù)據(jù)結(jié)構(gòu),
具體執(zhí)行流程如下
- 首先創(chuàng)建全局執(zhí)行上下文衅鹿, 壓入棧底
- 每當調(diào)用一個函數(shù)時,創(chuàng)建函數(shù)的函數(shù)執(zhí)行上下文大渤。并且壓入棧頂
- 當函數(shù)執(zhí)行完成后,會從執(zhí)行上下文棧中彈出泵三,js引擎繼續(xù)執(zhí)棧頂?shù)暮瘮?shù)。
如以下函數(shù)執(zhí)行時的執(zhí)行棧變化為:
function fun1(){
console.log('func1')
fun2()
}
function fun2(){
console.log('func2')
}
fun1()
/*
* fun2
* fun1 fun1 fun1
* global => global => global => global => global
*/
執(zhí)行上下文生命周期
變量對象VO和活動對象AO
在講生命周期錢俺抽。我們必須了解講個對象较曼,變量對象VO和活動對象AO
變量對象VO:
- 用來存儲執(zhí)行上下文中可以被訪問。但不能被delete的函數(shù)標識弛饭。
- 包括:arguments 對象,形參實參鍵值對孩哑,函數(shù)聲明,變量聲明和this
活動對象AO: - 他可以被理解為當函數(shù)被激活調(diào)用時創(chuàng)建的對象胳蛮。用來訪問VO對象中存儲的那些個標識丛晌。
全局上下文中變量對象: - 就是全局對象
- 初始化時:初始化一系列原始屬性:Math,String澎蛛,Date,Window等
- 在瀏覽器中window對象引用全局對象呆馁,全局環(huán)境中this也引用自身
創(chuàng)建階段
此階段執(zhí)行上下文會執(zhí)行以下操作
- 創(chuàng)建作用域鏈
- 通過變量對象VO創(chuàng)建活動AO毁兆,
- 首先創(chuàng)建arguments對象
- 創(chuàng)建形參實參的鍵值對
- 創(chuàng)建函數(shù)聲明 (經(jīng)典面試題:為什么函數(shù)聲明提前?)
- 創(chuàng)建變量聲明 (經(jīng)典面試題:為什么變量聲明提升纺腊?)
- 創(chuàng)建this
執(zhí)行階段
- 變量賦值茎芭。函數(shù)引用,執(zhí)行其他代碼邏輯
- 當執(zhí)行完畢后梅桩。執(zhí)行上下文出棧,等待垃圾回收機制回收
舉例說明
function a(name, age){
var a = 1
function c(){}
var d = funciton (){}
(function e(){})
var f = function g(){}
}
a('John')
// 如上代碼煮寡。
//在創(chuàng)建預(yù)編譯階段生成的AO對象如下
AO = {
arguments:{
0: 'John',
1: undefined,
length: 2
},
name: 'John',
age: undefined,
c: reference to function c(){},
a: undefined,
d: undefined,
f: undefined,
}
// 函數(shù)表達式 e犀呼,不在AO中
// 函數(shù)g不在AO中
// 函數(shù)執(zhí)行時AO對象如下
AO = {
arguments:{
0: 'John',
1: undefined,
length: 2
},
name: 'John',
age: undefined,
c: reference to function c(){},
a: 1,
d: reference to FunctionExpression "d",
f: reference to FunctionExpression "f",
}
函數(shù)聲明提前
從AO對象的創(chuàng)建過程我們就可以發(fā)現(xiàn)薇组,AO對象想是先掃描函數(shù)體內(nèi)的函數(shù)聲明才去掃描變量聲明。所以這也就是為啥會有聲明提前宋光。
變量提升
AO對象創(chuàng)建時已經(jīng)將函數(shù)內(nèi)部的變量提前掃描聲明。是指在函數(shù)執(zhí)行的過程中開始依次賦值罪佳。
詞法環(huán)境和變量環(huán)境
在ES6中提出詞法環(huán)境和變量環(huán)境兩個概念。主要是執(zhí)行上下文創(chuàng)建過程酌毡。
詞法環(huán)境(LexicalEnvironment)
詞法環(huán)境是一種包含 標識符 => 變量 隱射關(guān)系的一種結(jié)構(gòu)蕾管。
在詞法環(huán)境中有兩個組成部分:
- 環(huán)境記錄(EnvironmentRecord): 儲存變量和函數(shù)聲明的實際位置
- 對外部環(huán)境的引用(Outer):當前可以訪問的外部詞法環(huán)境
詞法環(huán)境分為兩種類型:
- 全局環(huán)境: 全局執(zhí)行上下文,他沒有外部環(huán)境的引用旭蠕,擁有一個全局對象window和關(guān)聯(lián)的方法和屬性eg: Math,String,Date等旷坦。還有用戶定義的全局變量,并將this指向全局對象秒梅。
- 函數(shù)環(huán)境: 用戶在函數(shù)定義的變量將儲存在環(huán)境記錄中。對外部環(huán)境的引用可以是全局環(huán)境岗屏,也可以是包含內(nèi)部函數(shù)的外部函數(shù)環(huán)境漱办。環(huán)境記錄中包含。用戶聲明的變量娩井。函數(shù)。還有arguments對象咐刨。
舉例詞法環(huán)境在偽代碼中如下:
GlobalExectionContent = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 剩余標識符
},
Outer: null,
}
}
FunctionExectionContent = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 剩余標識符
},
Outer: [Global or outer function environment reference],
}
}
變量環(huán)境(VariableEnvironment)
變量環(huán)境也是一個詞法環(huán)境扬霜。他具有詞法環(huán)境中所有的屬性
在ES6中,LexicalEnvironment和VariableEnvironment 的區(qū)別在于前者用于存儲函數(shù)聲明和變量let 和 const 綁定联予,而后者僅用于存儲變量 var 綁定。
用以下代碼舉例:
let a = 20;
const b = 30;
var c;
function add(e, f) {
var g = 20;
function c(){}
return e + f + g;
}
c = add(20, 30);
在預(yù)編譯階段沸久。生成的詞法環(huán)境和變量環(huán)境如下
GlobalExectionContent = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Object",
a: <uninitialied>,
b: <uninitialied>,
add: <func>
// 剩余標識符
},
Outer: null,
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
c: undefined,
// 剩余標識符
},
Outer: null,
}
}
FunctionExectionContent = {
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
arguments: {
0: 20,
1: 30,
length: 2,
},
e: 20,
f: 30,
c: reference to function c(){}
// 剩余標識符
},
Outer: GlobalLexicalEnvironment,
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
g: undefined,
// 剩余標識符
},
Outer: GlobalLexicalEnvironment,
}
}
我們發(fā)現(xiàn)使用let和const聲明的變量在詞法環(huán)境創(chuàng)建時是未賦值初始值卷胯。而使用var定義的變量在變量環(huán)境創(chuàng)建時賦值為undefined。這也就是為什么const窑睁、let聲明的變量在聲明錢調(diào)用會報錯,而var聲明的變量不會沙郭。
代碼執(zhí)行階段
此階段的執(zhí)行流程就是函數(shù)執(zhí)行時的流程裳朋。給變量賦值,和執(zhí)行其他邏輯代碼鲤嫡。