概念
- Execute Context:執(zhí)行上下文
- Execute Context Stack:執(zhí)行上下文棧(或 Call Stack 調(diào)用棧)脑题,存儲代碼運(yùn)行期間創(chuàng)建的所以上下文
- Event Loop:引擎運(yùn)行 js 線程的方式
- 引擎:從頭到尾負(fù)責(zé)整個 JavaScript 程序的編譯及執(zhí)行過程
- 編譯器:負(fù)責(zé)語法分析及代碼生成等
- 作用域:負(fù)責(zé)收集并維護(hù)由所有聲明的標(biāo)識符(變量)組成的一系列查詢菩暗,并實(shí)施一套非常嚴(yán)格的規(guī)則,確定當(dāng)前執(zhí)行的代碼對這些標(biāo)識符的訪問權(quán)限
JavaScript 的運(yùn)行分為兩部分:編譯旭蠕、執(zhí)行
一些問題
- 為什么使用棧結(jié)構(gòu)存儲運(yùn)行時的執(zhí)行上下文?
function second() { console.log(name); } function first() { var name = '2'; second(); } var name = '1'; first(); // 輸出:1
- 進(jìn)程與線程
進(jìn)程:資源分配的最小單位;進(jìn)程擁有獨(dú)立的堆椞桶荆空間和數(shù)據(jù)段佑稠,每當(dāng)啟動一個新的進(jìn)程必須分配給它獨(dú)立的地址空間,建立眾多的數(shù)據(jù)表來維護(hù)它的代碼段旗芬、堆棧段和數(shù)據(jù)段
線程:程序執(zhí)行的最小單位舌胶;線程擁有獨(dú)立的堆棧空間疮丛,但是共享數(shù)據(jù)段幔嫂,它們彼此之間使用相同的地址空間,共享大部分?jǐn)?shù)據(jù)誊薄,比進(jìn)程更節(jié)儉履恩,開銷比較小,切換速度也比進(jìn)程快呢蔫,效率高
JavaScript 的執(zhí)行分為兩個階段:1. 創(chuàng)建執(zhí)行上下文切心;2. 執(zhí)行代碼;
執(zhí)行上下文
執(zhí)行上下文有三種類型:全局執(zhí)行上下文片吊、函數(shù)執(zhí)行上下文绽昏、Eval 函數(shù)執(zhí)行上下文
執(zhí)行上下文的數(shù)據(jù)結(jié)構(gòu):
// ES3 例:
function foo(i) {
var a = 'hello';
var b = function bar() {
};
function c() {
}
}
foo(22);
創(chuàng)建階段
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
this: { ... }
}
ES3 的函數(shù)執(zhí)行上下文包括:
- scopeChain:指向上一個作用域(新概念)
- variableObject:包含函數(shù)的參數(shù)
arguments
和函數(shù)內(nèi)聲明的變量 - this:執(zhí)行函數(shù)的調(diào)用者
在創(chuàng)建階段函數(shù)內(nèi)聲明的變量都初始化為 undefined
,在執(zhí)行階段將在棧中存儲(數(shù)據(jù)段俏脊?)或堆中存儲的值賦給變量(說明有一個收集值的過程全谤,但在創(chuàng)建階段為什么就初始化了參數(shù)的值?)
函數(shù)每調(diào)用一次爷贫,都會產(chǎn)生一個新的執(zhí)行上下文環(huán)境认然。因此不同的調(diào)用可能就有不同的參數(shù)。
總結(jié):ES3 的執(zhí)行上下文
創(chuàng)建階段:1. 創(chuàng)建作用域鏈沸久;2. 創(chuàng)建變量對象VO(包括參數(shù)季眷,函數(shù),變量)卷胯;3. 確定this的值
激活/執(zhí)行階段:完成變量分配子刮,執(zhí)行代碼
ES5 的執(zhí)行上下文
FunctionExectionContext = {
this: <Global Object>,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative", // 環(huán)境記錄分類: 聲明環(huán)境記錄
Arguments: {0: 20, 1: 30, length: 2}, // 函數(shù)環(huán)境下,環(huán)境記錄比全局環(huán)境下的環(huán)境記錄多了argument對象
},
outer: <GlobalLexicalEnvironment>
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative", // 環(huán)境記錄分類: 聲明環(huán)境記錄
g: undefined
},
outer: <GlobalLexicalEnvironment>
}
}
一些問題
- 詞法作用域意味著作用域是由書寫代碼時函數(shù)聲明的位置來決定的窑睁。編譯的詞法分析階段基本能夠知道全部標(biāo)識符在哪里以及時如何聲明的挺峡,從而能夠預(yù)測在執(zhí)行過程中如何對它們進(jìn)行查找(找到所有的聲明,并用適合的左右域?qū)⑺鼈冴P(guān)聯(lián)起來)
// 包括變量和函數(shù)在內(nèi)的所有聲明都會在任何代碼被執(zhí)行之前首先被處理(變量提升)
// 函數(shù)優(yōu)先
foo()
var foo;
function foo() {
console.log('1');
}
foo = function() {
console.log('2')
}
// 這段代碼會被*引擎*理解為如下形式
function foo() { // 變量提升 函數(shù)優(yōu)先
console.log('1')
}
// var foo = undefined 重復(fù)的變量聲明 被忽略
foo(); // 1
foo = function() {
console.log('2');
}
在傳統(tǒng)編譯語言的流程中担钮,程序的一段源代碼執(zhí)行之前會經(jīng)歷三個步驟橱赠,統(tǒng)稱為”編譯“
- 詞法分析:將代碼的字符串分解成詞法單元(token)
- 語法分析:將詞法單元流(數(shù)組)轉(zhuǎn)換成抽象語法樹 AST
- 代碼生成:將 AST 轉(zhuǎn)換成可執(zhí)行代碼
任何 JavaScript 代碼片段在執(zhí)行前都要進(jìn)行編譯(通常就在執(zhí)行前)
-
var
重復(fù)聲明變量會被忽略、不允許使用let
const
重復(fù)聲明變量箫津,function
重復(fù)聲明的變量后面會覆蓋前面
作用域:全局作用域狭姨、函數(shù)作用域宰啦、塊作用域({}
創(chuàng)建,不是 object)
let
饼拍、const
將變量綁定塊級作用域
let
赡模、const
的暫時死區(qū):
console.log(a); // undefined (變量提升)
console.log(b); // ReferenceError: Cannot access 'b' before initialization (暫時死區(qū))
var a = 1;
const b = 2;
塊作用域里的函數(shù)聲明
foo(); // b
var a = true;
if(a) {
function foo() {console.log('a')}
} else {
function foo() {console.log('b')}
}
作用域和執(zhí)行上下文的區(qū)別
- 作用域:函數(shù)聲明時確定
- 執(zhí)行上下文:函數(shù)每調(diào)用一次,都會產(chǎn)生一個新的執(zhí)行上下文環(huán)境师抄,處于活動狀態(tài)的執(zhí)行上下文環(huán)境只有一個漓柑。(在你不知道的JavaScript這本書中,執(zhí)行上下文被叫做動態(tài)作用域)
變量叨吮、函數(shù)表達(dá)式——變量聲明(默認(rèn)賦值為undefined)
this——賦值辆布;
函數(shù)聲明——賦值;
this
this 屬于執(zhí)行上下文的一個屬性茶鉴;由于每次調(diào)用函數(shù)都會生成一個新的執(zhí)行上下文锋玲,所以每次調(diào)用函數(shù)都會給 this 賦值;this 表示函數(shù)被誰調(diào)用
箭頭函數(shù)
用當(dāng)前的詞法作用域覆蓋了 this 本來的值蛤铜;this 的值同當(dāng)前詞法作用域?qū)?yīng)的執(zhí)行上下文的 this 的值嫩絮;