接下來,我們以一個(gè)簡(jiǎn)單例子進(jìn)行分析承二。
var a = 2;
function bar() {
? ? var b = 2;
? ? function foo() {
? ? ? ? var c = 2;
? ? }
? ? foo();
}
bar();
1. JS引擎創(chuàng)建一個(gè)全局對(duì)象(Global Object)
這個(gè)對(duì)象全局只存在一份莱找,它的屬性在任何地方都可以訪問皿淋,它的存在伴隨著應(yīng)用程序的整個(gè)生命周期帝洪。全局對(duì)象在創(chuàng)建時(shí)哲身,將Math,String,Date,document 等常用的JS對(duì)象作為其屬性。由于這個(gè)全局對(duì)象不能通過名字直接訪問衰腌,因此還有另外一個(gè)屬性window,并將window指向了自身新蟆,這樣就可以通過window訪問這個(gè)全局對(duì)象了。用偽代碼模擬全局對(duì)象的大體結(jié)構(gòu)如下:
//創(chuàng)建一個(gè)全局對(duì)象
var globalObject = {
? ? Math:{},
? ? String:{},
? ? Date:{},
? ? document:{}, //DOM操作
? ? ...
? ? window:this //讓window屬性指向了自身
}
2. JS引擎會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境棧(Execution Context Stack)
棧
提到棧右蕊,小伙伴們都知道琼稻,棧是一種類似羽毛球筒存儲(chǔ)羽毛球的數(shù)據(jù)結(jié)構(gòu),采用先進(jìn)后出饶囚,后進(jìn)先出的特點(diǎn)帕翻。
上圖中的羽毛球1一定是先放入棧中,然后是羽毛球2萝风,以此類推嘀掸,而出棧時(shí),一定是羽毛球5先拿出來规惰,然后是羽毛球4睬塌,以此類推,這種方式和棧存取數(shù)據(jù)的方式如出一轍歇万。
堆
堆數(shù)據(jù)類型類似與書架揩晴。書雖然也整齊的存放在書架上,但是我們只要知道書的名字贪磺,我們就可以很方便的取出我們想要的書硫兰。
好了好了,扯遠(yuǎn)了寒锚。我們接著往下說劫映,在這只需知道執(zhí)行環(huán)境棧是怎樣存取數(shù)據(jù)的就行。
3. 創(chuàng)建全局執(zhí)行上下文(Execution Context)
到這你可能會(huì)問刹前,上下文是個(gè)啥玩意苏研?
是啊,上下文是個(gè)什么鬼叭肌?
上下文不是玩意筹燕,也不是什么鬼皆警。
執(zhí)行上下文可以理解為當(dāng)前代碼的執(zhí)行環(huán)境含滴。JS所有代碼都會(huì)在自己的上下文環(huán)境下運(yùn)行。
說到上下文,你可能會(huì)有這樣的疑惑:上下文不就是作用域嗎贾陷?
老鐵,我肯定的告訴你涌萤,上下文不是作用域酷窥。的確,在JS里,這還真是個(gè)很難區(qū)分的東東衔掸。不過現(xiàn)在我還不能馬上道出他們的區(qū)別烫幕,因?yàn)樽饔糜虻闹R(shí),我們還沒有涉及敞映,??徹底搞懂JavaScript作用域较曼,通過這篇文章,你將徹徹底底了解關(guān)于作用域的一切振愿。
那在JS中會(huì)有幾種執(zhí)行環(huán)境呢捷犹?
大概有3種:
全局環(huán)境:JavaScript代碼運(yùn)行起來會(huì)首先進(jìn)入該環(huán)境
函數(shù)環(huán)境:當(dāng)函數(shù)被調(diào)用執(zhí)行時(shí),會(huì)進(jìn)入當(dāng)前函數(shù)中執(zhí)行代碼
eval冕末、with(不建議使用萍歉,可忽略)
因此在一個(gè)JavaScript程序中,必定會(huì)產(chǎn)生多個(gè)執(zhí)行上下文档桃。
go on...
4. 全局上下文推入執(zhí)行環(huán)境棧底
5. 代碼開始從上往下執(zhí)行枪孩,這里我們暫且不談標(biāo)識(shí)符處理,當(dāng)代碼執(zhí)行到bar(),生成bar執(zhí)行上下文胳蛮,推入棧中
6. 代碼執(zhí)行到foo(),生成foo執(zhí)行上下文销凑,推入棧中
7. foo()執(zhí)行完,foo執(zhí)行上下文出棧
8. bar()執(zhí)行完仅炊,bar執(zhí)行上下文出棧
9. 全局上下文執(zhí)行上下文出棧
我們用圖走一下js執(zhí)行流程斗幼,是這樣的:
小伙伴們,現(xiàn)在是不是對(duì)JS執(zhí)行流程有了一個(gè)整體認(rèn)識(shí)抚垄,下面我們來說點(diǎn)更有意思的蜕窿。
上下文執(zhí)行細(xì)節(jié)
我們先看整體了解下
創(chuàng)建階段
1. 創(chuàng)建變量對(duì)象(Variable Object)
創(chuàng)建變量對(duì)象,依次經(jīng)歷了以下幾個(gè)步驟
建立arguments對(duì)象呆馁。檢測(cè)當(dāng)前上下文參數(shù)桐经,建立該對(duì)對(duì)象下的屬性及屬性值。(這里提一下浙滤,函數(shù)的參數(shù)是按值傳遞阴挣,我知道你是知道的)
檢測(cè)關(guān)鍵詞function函數(shù)聲明。檢測(cè)當(dāng)前上下文中的函數(shù)聲明纺腊,并掛載到變量對(duì)象上畔咧,其值是函數(shù)對(duì)象的引用。
檢測(cè)var變量聲明揖膜。檢測(cè)當(dāng)前上下文中的var聲明誓沸,并賦值為undefined;如遇到同名var聲明的變量,則會(huì)默認(rèn)覆蓋;如遇到同名函數(shù)聲明壹粟,則默認(rèn)忽略拜隧,這也就體現(xiàn)了函數(shù)聲明的優(yōu)先級(jí)要高于var聲明。誰的大哥還是得分清的,哈哈洪添。垦页。。
變量提升
看到這薇组,我覺得你對(duì)變量提升具體是什么以及如何實(shí)現(xiàn)的應(yīng)該了解的一清二楚了外臂。
是不是呢?
我們來一道題測(cè)試下
function foo() {
? ? console.log(a);
? ? console.log(baz);
? ? var a = 'inner';
? ? var baz = 1;
? ? function baz() {}
}
foo();
第一處是undefined律胀,第二處是[Function: baz]宋光,是不是很簡(jiǎn)單?
下面我用代碼簡(jiǎn)單模擬下上面的過程
function foo() {
? ? function baz() {}
? ? var a = undefined;
? ? console.log(a);
? ? a = 'inner';
? ? console.log(baz);
}
foo();
變量對(duì)象大概是這樣的
VO(foo) = {
? ? arguments: {},
? ? baz: ,? // 表示foo的地址引用
? ? a: undefined
}
2. 確定作用域鏈
作用域鏈?zhǔn)怯僧?dāng)前作用域與上層一系列父級(jí)作用域組成炭菌,作用域的頭部永遠(yuǎn)是當(dāng)前作用域罪佳,尾部永遠(yuǎn)是全局作用域。作用域鏈保證了當(dāng)前上下文對(duì)其有權(quán)訪問的變量的有序訪問黑低。
我們先簡(jiǎn)單了解下赘艳,詳細(xì)的我們會(huì)在徹底搞懂JavaScript作用域中談到。
var a = 1;
function foo() {
? ? function baz() {
? ? ? ? console.log( a );
? ? }
? ? baz();
}
foo(); // 1
上面的對(duì)于我們來說很簡(jiǎn)單克握,是吧蕾管?沒錯(cuò)這就是作用域鏈的應(yīng)用。
我們簡(jiǎn)單模擬下
EC(foo) = {
? ? VO(foo): {...}, //省略
? ? ScopeChain: [VO(foo), window],
? ? this:
}
EC(baz) = {
? ? VO(baz): {...}, //省略
? ? ScopeChain: [VO(baz), VO(foo), window],
? ? this:
}
3. 確定this指向
談到this菩暗,大家是不是感到很興奮掰曾,平時(shí)寫代碼時(shí),被這家伙整的暈頭轉(zhuǎn)向的停团,這回我們終于可以揭開this的神秘面紗了旷坦,搞清楚它在JS到底是怎樣的存在,不過客官別著急佑稠,我們這里先不介紹this秒梅,因?yàn)殛P(guān)于this的內(nèi)容太多了,我們得慢慢去品味它舌胶,這里先記住捆蜀,this是在執(zhí)行上下文創(chuàng)建階段確定的。
this傳送門??????
全局上下文
全局上下文有些特殊幔嫂,其變量對(duì)象永遠(yuǎn)是window漱办,this永遠(yuǎn)指向window(在瀏覽器中,Node中不是)婉烟。
即
EC(global) = {
? ? VO: window,
? ? ScopeChain: {},
? ? this: window
}
執(zhí)行階段
在執(zhí)行階段變量對(duì)象(Variable Object)變?yōu)榛顒?dòng)對(duì)象(Active Object)。
VO => AO
這樣暇屋,如果再面試的時(shí)候被問到變量對(duì)象和活動(dòng)對(duì)象有什么區(qū)別似袁,就又可以自如的應(yīng)答了,他們其實(shí)都是同一個(gè)對(duì)象,只是處于執(zhí)行上下文的不同生命周期昙衅。不過只有處于函數(shù)調(diào)用棧棧頂?shù)膱?zhí)行上下文中的變量對(duì)象扬霜,才會(huì)變成活動(dòng)對(duì)象。
執(zhí)行階段JS引擎會(huì)進(jìn)行變量賦值而涉、函數(shù)引用著瓶、執(zhí)行其他代碼,執(zhí)行順序取決于代碼的位置啼县。