本文涵蓋的知識(shí)點(diǎn): 執(zhí)行上下文和執(zhí)行上下文棧句惯,作用域和作用域鏈玲销,變量對(duì)象输拇,活動(dòng)對(duì)象,this, apply, call等贤斜,請(qǐng)事先學(xué)習(xí)相關(guān)基礎(chǔ)知識(shí)
首先我們寫了一段代碼如下策吠,很簡(jiǎn)單對(duì)不對(duì),執(zhí)行步驟可以看到注釋瘩绒,下面我們就一步步分析在執(zhí)行中做了什么猴抹,抓穩(wěn)扶好哦!锁荔!
// -------------- 第1步 ----------------------//
var jason = "global jason";
function jasonzeng(){
var jason = "local jason";
function f(){
return jason;
}
// -------------- 第3步 ---------------//
return f();
// -------------- 第4步 --------------------//
}
// ------------------- 第2步 --------------------//
jasonzeng();
// --------------- 第5步 ---------------------//
準(zhǔn)備階段: 在代碼執(zhí)行之前蟀给,js環(huán)境會(huì)創(chuàng)建一個(gè)執(zhí)行上下文棧的東西: ExecuteContextStack
,這個(gè)堆棧結(jié)構(gòu)為真正執(zhí)行做好了準(zhǔn)備阳堕,所有執(zhí)行代碼需要的東西都從這個(gè)棧里面取, 因?yàn)榇a還沒有執(zhí)行跋理,所以現(xiàn)在這個(gè)棧還是空的
ExecuteContextStack = [];
第一步: 此時(shí)開始執(zhí)行全局代碼,此時(shí)會(huì)創(chuàng)建全局的執(zhí)行上下文: GlobalContext
恬总,同時(shí)全局執(zhí)行上下文被壓入執(zhí)行上下文棧
什么時(shí)候創(chuàng)建執(zhí)行上下文: js的只有在全局代碼執(zhí)行前普,函數(shù)執(zhí)行,Eval執(zhí)行的時(shí)候才會(huì)創(chuàng)建執(zhí)行上下文, 說(shuō)白了越驻,這三種情況會(huì)創(chuàng)建獨(dú)立的作用域汁政,所以需要新創(chuàng)建上下文來(lái)保存當(dāng)前環(huán)境的需要的東西
ExecuteContextStack = [
GlobalContext
]
初始化全局執(zhí)行上下文: 此時(shí)GlobalContext
是什么呢道偷,里面放的就是執(zhí)行全局代碼需要的東西啦,這里面有三個(gè)非常重要的東西记劈,變量對(duì)象(Variable Object), 作用域(Scope), This, 初始化后如下, 注意這里jasonzeng
函數(shù)初始化的時(shí)內(nèi)部有個(gè)屬性[[scope]]
同樣保存了當(dāng)前函數(shù)的作用域鏈勺鸦,這是閉包能實(shí)現(xiàn)的根本原因
vo, scope, this 的關(guān)系: 最重要的其實(shí)是變量對(duì)象,這里簡(jiǎn)稱
VO
,VO
決定了當(dāng)前執(zhí)行環(huán)境里面有什么目木,而Scope
其實(shí)是VO
的一個(gè)從內(nèi)到外的鏈?zhǔn)綌?shù)組换途,用處就是假如在當(dāng)前執(zhí)行環(huán)境中需要的對(duì)象或函數(shù)時(shí),當(dāng)前VO中沒有刽射,就會(huì)順著鏈條往上找這個(gè)對(duì)象或函數(shù)军拟,而最后的this呢,則是指向正在執(zhí)行的OV
, 注意this
不代表就一定指向當(dāng)前上下文的OV
誓禁,比如當(dāng)用call
或者apply
綁定this
到指定對(duì)象的時(shí)候懈息,這是這個(gè)this
就指向相應(yīng)對(duì)像執(zhí)行上下文的OV
, 個(gè)人理解,歡迎拍磚D∏ 辫继!
ExecuteContextStack = [
globalContext = {
VO : [
jason,
jasonzeng = {
[[scope]] : globalContext.VO
}
],
scope : [
globalContext.VO
],
this : globalContext.VO
}
];
第二步: 初始化完全局執(zhí)行上下文就開始執(zhí)行了,首先執(zhí)行的是jasonzeng()
函數(shù)俗慈, 還記得上面說(shuō)的嗎姑宽? 在函數(shù)執(zhí)行的時(shí)候同時(shí)會(huì)創(chuàng)建執(zhí)行上下文jasonzengContext
,此時(shí)因?yàn)橐獔?zhí)行函數(shù)了,里面變量對(duì)象的VO
就變成ActiveObject
, 簡(jiǎn)稱AO
闺阱,因?yàn)槭呛瘮?shù)有參數(shù)炮车,所以比較特殊,會(huì)默認(rèn)創(chuàng)建一個(gè)arguments
的數(shù)組來(lái)保存參數(shù)酣溃,其他的變量和函數(shù)聲明 類似瘦穆,注意現(xiàn)在只是聲明,沒有創(chuàng)建救拉, 還需要注意的是此時(shí)函數(shù)的scope
作用域鏈即保存了當(dāng)前的AO又有上一層全局的VO, 構(gòu)成完整的作用域鏈难审,當(dāng)jasonzengContext
初始化完后也會(huì)壓入執(zhí)行上下文棧ExecuteContextStack
,現(xiàn)在完整的棧如下
- VO和AO的區(qū)別: 未進(jìn)入執(zhí)行階段之前亿絮,變量對(duì)象(VO)中的屬性都不能訪問(wèn)告喊!但是進(jìn)入執(zhí)行階段之后,變量對(duì)象(VO)轉(zhuǎn)變?yōu)榱嘶顒?dòng)對(duì)象(AO)派昧,里面的屬性都能被訪問(wèn)了黔姜,然后開始進(jìn)行執(zhí)行階段的操作, 它們其實(shí)都是同一個(gè)對(duì)象蒂萎,只是處于執(zhí)行上下文的不同生命周期
- 作用域鏈的創(chuàng)建: 其實(shí)就是把函數(shù)初始化的時(shí)候創(chuàng)建的內(nèi)部變量
[[scope]]
的鏈復(fù)制到當(dāng)前函數(shù)的執(zhí)行上下文的scope上面秆吵,并且在最前端加入當(dāng)前的AO構(gòu)成完整的作用域鏈
ExecuteContextStack = [
jasonzengContext = {
AO : [
arguments = {
length : 0
},
jason = undefined,
f = function f() {}
],
Scope : [
jasonzengContext.AO , globalContext.VO
],
this : undefined
},
globalContext = {
VO : [
global,
jason,
jasonzeng = {
[[scope]] : globalContext.VO
}
],
Scope : [
globalContext.VO
],
this : globalContext.VO
}
];
第三步: 開始執(zhí)行jasonzeng
函數(shù)內(nèi)部代碼,此時(shí)jasonzengContext
上面的AO對(duì)象會(huì)根據(jù)代碼更改為相應(yīng)的值五慈,比如當(dāng)執(zhí)行到f()
時(shí)候纳寂,AO函數(shù)內(nèi)部的jason
會(huì)被賦值為jason = "local jason",
開始執(zhí)行f()
的時(shí)候跟上一步一樣主穗,初始化f函數(shù)的執(zhí)行上下文fContext
,特別注意的是里面的scope
作用域鏈毙芜, 最后再壓入執(zhí)行上下文棧如下
ExecuteContextStack = [
fContext = {
AO : [
arguments = {
length : 0
}
],
Scope : [fContext.AO, jasonzengContext.AO, globalContext.VO],
this : undefined
},
jasonzengContext = {
AO : [
arguments = {
length : 0
},
jason = "local jason",
f = function f() {}
],
Scope : [
jasonzengContext.AO , globalContext.VO
],
this : jasonzengContext.AO
},
globalContext = {
VO : [
global,
jason,
jasonzeng = {
[[scope]] : globalContext.VO
}
],
Scope : [
globalContext.VO
],
this : globalContext.VO
}
];
第四步: 此時(shí)進(jìn)入函數(shù)f
內(nèi)部執(zhí)行了忽媒,發(fā)現(xiàn)終于沒有再內(nèi)嵌函數(shù)了,所以不用再創(chuàng)建執(zhí)行上下文了腋粥,那么執(zhí)行完f
函數(shù)后它的執(zhí)行上下文fContext
就肯定不用了撒晦雨,然后就從執(zhí)行上下文棧中彈出,彈出后執(zhí)行上下文棧如下
ExecuteContextStack = [
jasonzengContext,
globalContext
];
第五步: 同上隘冲,執(zhí)行完jasonzeng
函數(shù)闹瞧,執(zhí)行上下文從執(zhí)行上下文棧中彈出如下, 這樣整個(gè)代碼執(zhí)行過(guò)程就講述完了展辞,是不是意猶未盡啊
ExecuteContextStack = [
globalContext
];
總結(jié): 最后總結(jié)一下奥邮,我們可以看到,在整個(gè)代碼執(zhí)行前和執(zhí)行時(shí)和執(zhí)行后都在對(duì)這個(gè)執(zhí)行上下文棧做操作罗珍,因?yàn)槲覀冊(cè)趫?zhí)行時(shí)需要的東西都在上面漠烧,當(dāng)前作用域擁有的變量可以從AO上面獲取, 閉包獲取的變量從作用域鏈上游獲取靡砌,this也可以直接獲取,這樣很多js的各種奇葩現(xiàn)象都可以解釋通了珊楼,這就是由里及外學(xué)習(xí)的好處呀通殃,加油各位愛碼士!2拮凇画舌!