摘要:延長(zhǎng)作用域鏈下面兩種語(yǔ)句可以在作用域鏈的前端臨時(shí)增加一個(gè)變量對(duì)象以延長(zhǎng)作用域鏈,
問(wèn)題
今天看筆記發(fā)現(xiàn)自己之前記了一個(gè)關(guān)于同名標(biāo)識(shí)符優(yōu)先級(jí)的內(nèi)容,具體是下面這樣的:
形參優(yōu)先級(jí)高于當(dāng)前函數(shù)名,低于內(nèi)部函數(shù)名
形參優(yōu)先級(jí)高于arguments
形參優(yōu)先級(jí)高于只聲明卻未賦值的局部變量,但是低于聲明且賦值的局部變量
函數(shù)和變量都會(huì)聲明提升,函數(shù)名和變量名同名時(shí),函數(shù)名的優(yōu)先級(jí)要高。執(zhí)行代碼時(shí),同名函數(shù)會(huì)覆蓋只聲明卻未賦值的變量,但是它不能覆蓋聲明且賦值的變量
局部變量也會(huì)聲明提升,可以先使用后聲明,不影響外部同名變量
然后我就想,為什么會(huì)有這樣的優(yōu)先級(jí)呢,規(guī)定的?但是好像沒(méi)有這個(gè)規(guī)定,于是開(kāi)始查閱資料,就有了下文
初識(shí)Execution ContextExecution Context是Javascript中一個(gè)抽象概念,它定義了變量或函數(shù)有權(quán)訪問(wèn)的其他數(shù)據(jù),決定了它們各自的行為。為了便于理解,我們可以近似將其等同于執(zhí)行當(dāng)前代碼的環(huán)境,JavaScript的可執(zhí)行代碼包括
全局代碼
函數(shù)代碼
eval()代碼
每當(dāng)執(zhí)行流轉(zhuǎn)到這些可執(zhí)行代碼時(shí),就會(huì)“新建”一個(gè)Execution Context并進(jìn)入該Execution Context
在上圖中,共有4個(gè)Execution Context,其中有一個(gè)是Global Execution Context(有且僅有一個(gè)),還有三個(gè)Function Execution Context
再識(shí)Execution Context Stack瀏覽器中的JavaScript解釋器是單線程的,每次創(chuàng)建并進(jìn)入一個(gè)新的Execution Context時(shí),這個(gè)Execution Context就會(huì)被推(push)進(jìn)一個(gè)環(huán)境棧中,這個(gè)棧稱為Execution Context Stack,當(dāng)當(dāng)前Execution Context的代碼執(zhí)行完之后,棧又會(huì)將其彈(pop)出,并銷毀這個(gè)Execution Context,保存在其中的變量及函數(shù)定義也隨之被銷毀,然后把控制權(quán)返回給之前的Execution Context(Global Execution Context例外,它要等到應(yīng)用程序退出后 —— 如關(guān)閉網(wǎng)頁(yè)或?yàn)g覽器 —— 才會(huì)被銷毀)
JavaScript的執(zhí)行流就是由這個(gè)機(jī)制控制的,以下面的代碼為例說(shuō)明:
var sayHello = "Hello"; function name(){ var fisrtName = "Cao", lastName = "Cshine"; function getFirstName(){ return fisrtName; } function getLatName(){ return lastName; } console.log(sayHello + getFirstName() + " " + getLastName()); } name();
當(dāng)瀏覽器第一次加載script的時(shí)候,默認(rèn)會(huì)進(jìn)入Global Execution Context,所以Global Execution Context永遠(yuǎn)是在棧的最下面。
然后遇到函數(shù)調(diào)用name(),此時(shí)新建并進(jìn)入Function Execution Context name,Function Execution Context name入棧;
繼續(xù)執(zhí)行遇到函數(shù)調(diào)用getFirstName(),于是新建并進(jìn)入Function Execution Context getFirstName,Function Execution Context getFirstName入棧,由于該函數(shù)內(nèi)部不會(huì)再新建其他Execution Context,所以直接執(zhí)行完畢,然后出棧,控制權(quán)交給Function Execution Context name;
再往下執(zhí)行遇到函數(shù)調(diào)用getLastName(),于是新建并進(jìn)入Function Execution Context getLastName,Function Execution Context getLastName入棧,由于該函數(shù)內(nèi)部不會(huì)再新建其他Execution Context,所以直接執(zhí)行完畢,然后出棧,控制權(quán)交給Function Execution Context name;
執(zhí)行完console后,函數(shù)name也執(zhí)行完畢,于是出棧,控制權(quán)交給Function Execution Context name,至此棧中又只有Global Execution Context了
關(guān)于Execution Context Stack有5個(gè)關(guān)鍵點(diǎn):
單線程
同步執(zhí)行(非異步)
1個(gè)Global Execution Context
無(wú)限制的函數(shù)Function Execution Context
每個(gè)函數(shù)調(diào)用都會(huì)創(chuàng)建新的Execution Context,即使是自己調(diào)用自己,如下面的代碼:
(function foo(i) { if (i === 3) { return; } else { foo(++i); } }(0));
Execution Context Stack的情況如下圖所示:
親密接觸Execution Context每個(gè)Execution Context在概念上可以看成由下面三者組成:
變量對(duì)象(Variable object,簡(jiǎn)稱VO)
作用域鏈(Scope Chain)
this
變量對(duì)象(Variable object)該對(duì)象與Execution Context相關(guān)聯(lián),保存著Execution Context中定義的所有變量、函數(shù)聲明以及函數(shù)形參,這個(gè)對(duì)象我們無(wú)法訪問(wèn),但是解析器在后臺(tái)處理數(shù)據(jù)是用到它(注意函數(shù)表達(dá)式以及沒(méi)用var/let/const聲明的變量不在VO中)
Global Execution Context中的變量對(duì)象VO根據(jù)宿主環(huán)境的不同而不同,在瀏覽器中為window對(duì)象,因此所有的全局變量和函數(shù)都是作為window對(duì)象的屬性和方法創(chuàng)建的。
對(duì)于Function Execution Context,變量對(duì)象VO為函數(shù)的活動(dòng)對(duì)象,活動(dòng)對(duì)象是在進(jìn)入Function Execution Context時(shí)創(chuàng)建的,它通過(guò)函數(shù)的arguments屬性初始化,也就是最初只包含arguments這一個(gè)屬性。
在JavaScript解釋器內(nèi)部,每次調(diào)用Execution Context都會(huì)經(jīng)歷下面兩個(gè)階段:
創(chuàng)建階段(發(fā)生在函數(shù)調(diào)用時(shí),但是內(nèi)部代碼執(zhí)行前,這將解釋聲明提升現(xiàn)象)
創(chuàng)建作用域鏈(作用域鏈見(jiàn)下文)
創(chuàng)建變量對(duì)象VO
確定this的值
激活/代碼執(zhí)行階段
變量賦值、執(zhí)行代碼
其中創(chuàng)建階段的第二步創(chuàng)建變量對(duì)象VO的過(guò)程可以理解成下面這樣:
(Global Execution Context中沒(méi)有這一步) 創(chuàng)建arguments對(duì)象,掃描函數(shù)的所有形參,并將形參名稱 和對(duì)應(yīng)值組成的鍵值對(duì)作為變量對(duì)象VO的屬性。如果沒(méi)有傳遞對(duì)應(yīng)的實(shí)參,將undefined作為對(duì)應(yīng)值。如果形參名為arguments,將覆蓋arguments對(duì)象
掃描Execution Context中所有的函數(shù)聲明(注意是函數(shù)聲明,函數(shù)表達(dá)式不算)
將函數(shù)名和對(duì)應(yīng)值(指向內(nèi)存中該函數(shù)的引用指針)組成組成的鍵值對(duì)作為變量對(duì)象VO的屬性
如果變量對(duì)象VO已經(jīng)存在同名的屬性,則覆蓋這個(gè)屬性
掃描Execution Context中所有的變量聲明
由變量名和對(duì)應(yīng)值(此時(shí)為undefined) 組成,作為變量對(duì)象的屬性
如果變量名與已經(jīng)聲明的形參或函數(shù)相同,此時(shí)什么都不會(huì)發(fā)生,變量聲明不會(huì)干擾已經(jīng)存在的這個(gè)同名屬性。
好~~現(xiàn)在我們來(lái)看代碼捋一遍:
function foo(num) { console.log(num);// 66 console.log(a);// undefined console.log(b);// undefined console.log(fc);// f function fc() {} var a = "hello"; var b = function fb() {}; function fc() {} } foo(66);
當(dāng)調(diào)用foo(66)時(shí),創(chuàng)建階段時(shí),Execution Context可以理解成下面這個(gè)樣子
fooExecutionContext = { scopeChain: { ... }, variableObject: { arguments: { 0: 66, length: 1 }, num: 66, fc: pointer to function fc() a: undefined, b: undefined }, this: { ... } }
當(dāng)創(chuàng)建階段完成以后,執(zhí)行流進(jìn)入函數(shù)內(nèi)部,激活執(zhí)行階段,然后代碼完成執(zhí)行,Execution Context可以理解成下面這個(gè)樣子:
fooExecutionContext = { scopeChain: { ... }, variableObject: { arguments: { 0: 66, length: 1 }, num: 66, fc: pointer to function fc() a: "hello", b: pointer to function fb() }, this: { ... } }作用域鏈(Scope Chain)
當(dāng)代碼在一個(gè)Execution Context中執(zhí)行時(shí),就會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈,作用域鏈的用途是保證對(duì)執(zhí)行環(huán)境有權(quán)訪問(wèn)的所有變量和函數(shù)的有序訪問(wèn)
Global Execution Context中的作用域鏈只有Global Execution Context的變量對(duì)象(也就是window對(duì)象),而Function Execution Context中的作用域鏈還會(huì)有“父”Execution Context的變量對(duì)象,這里就會(huì)要牽扯到[[Scopes]]屬性,可以將函數(shù)作用域鏈理解為---- 當(dāng)前Function Execution Context的變量對(duì)象VO(也就是該函數(shù)的活動(dòng)對(duì)象AO) + [[Scopes]],怎么理解呢,我們繼續(xù)往下看
[[Scopes]]屬性[[Scopes]]這個(gè)屬性與函數(shù)的作用域鏈有著密不可分的關(guān)系,JavaScript中每個(gè)函數(shù)都表示為一個(gè)函數(shù)對(duì)象,[[Scopes]]是函數(shù)對(duì)象的一個(gè)內(nèi)部屬性,只有JavaScript引擎可以訪問(wèn)。
結(jié)合函數(shù)的生命周期:
函數(shù)定義
[[Scopes]]屬性在函數(shù)定義時(shí)被存儲(chǔ),保持不變,直至函數(shù)被銷毀
[[Scopes]]屬性鏈接到定義該函數(shù)的作用域鏈上,所以他保存的是所有包含該函數(shù)的 “父/祖父/曾祖父...” Execution Context的變量對(duì)象(OV),我們將其稱為所有父變量對(duì)象(All POV)
!!!特別注意 [[Scopes]]是在定義一個(gè)函數(shù)的時(shí)候決定的
函數(shù)調(diào)用
函數(shù)調(diào)用時(shí),會(huì)創(chuàng)建并進(jìn)入一個(gè)新的Function Execution Context,根據(jù)前面討論過(guò)的調(diào)用Function Execution Context的兩個(gè)階段可知:先創(chuàng)建作用域鏈,這個(gè)創(chuàng)建過(guò)程會(huì)將該函數(shù)對(duì)象的[[Scopes]]屬性加入到其中
然后會(huì)創(chuàng)建該函數(shù)的活動(dòng)對(duì)象AO(作為該Function Execution Context的變量對(duì)象VO),并將創(chuàng)建的這個(gè)活動(dòng)對(duì)象AO加到作用域鏈的最前端
然后確定this的值
正式執(zhí)行函數(shù)內(nèi)的代碼
通過(guò)上面的過(guò)程我們大概可以理解:作用域鏈 = 當(dāng)前Function Execution Context的變量對(duì)象VO(也就是該函數(shù)的活動(dòng)對(duì)象AO) + [[Scopes]],有了這個(gè)作用域鏈, 在發(fā)生標(biāo)識(shí)符解析的時(shí)候, 就會(huì)沿著作用域鏈一級(jí)一級(jí)地搜索標(biāo)識(shí)符,最開(kāi)始是搜索當(dāng)前Function Execution Context的變量對(duì)象VO,如果沒(méi)有找到,就會(huì)根據(jù)[[Scopes]]找到父變量對(duì)象,然后繼續(xù)搜索該父變量對(duì)象中是否有該標(biāo)識(shí)符;如果仍沒(méi)有找到,便會(huì)找到祖父變量對(duì)象并搜索其中是否有該標(biāo)識(shí)符;如此一級(jí)級(jí)的搜索,直至找到標(biāo)識(shí)符為止(如果直到最后也找不到,一般會(huì)報(bào)未定義的錯(cuò)誤);注意:對(duì)于this與arguments,只會(huì)搜到其本身的變量(活動(dòng))對(duì)象為止,而不會(huì)繼續(xù)按著作用域鏈搜素。
現(xiàn)在再結(jié)合例子來(lái)捋一遍:
var a = 10; function foo(d) { var b = 20; function bar() { var c = 30; console.log(a + b + c + d); // 110 //這里可以訪問(wèn)a,b,c,d } //這里可以訪問(wèn)a,b,d 但是不能訪問(wèn)c bar(); } //這里只能訪問(wèn)a foo(50);
當(dāng)瀏覽器第一次加載script的時(shí)候,默認(rèn)會(huì)進(jìn)入Global Execution Context的創(chuàng)建階段
創(chuàng)建Scope Chain(作用域鏈)
創(chuàng)建變量對(duì)象,此處為window對(duì)象。然后會(huì)掃描所有的全局函數(shù)聲明,再掃描全局變量聲明。之后該變量對(duì)象會(huì)加到Scope Chain中
確定this的值
此時(shí)Global Execution Context可以表示為:
globalEC = { scopeChain: { pointer to globalEC.VO }, VO: { a: undefined, foo: pointer to function foo(), (其他window屬性) }, this: { ... } }
接著進(jìn)入Global Execution Context的執(zhí)行階段
遇到賦值語(yǔ)句var a = 10,于是globalEC.VO.a = 10;
globalEC = { scopeChain: { pointer to globalEC.VO }, VO: { a: 10, foo: pointer to function foo(), (其他window屬性) }, this: { ... } }
遇到foo函數(shù)定義語(yǔ)句,進(jìn)入foo函數(shù)的定義階段,foo的[[Scopes]]屬性被確定
foo.[[Scopes]] = { pointer to globalEC.VO }
遇到foo(50)調(diào)用語(yǔ)句,進(jìn)入foo函數(shù)調(diào)用階段,此時(shí)進(jìn)入Function Execution Context foo的創(chuàng)建階段
創(chuàng)建Scope Chain(作用域鏈)
創(chuàng)建變量對(duì)象,此處為foo的活動(dòng)對(duì)象。先創(chuàng)建arguments對(duì)象,然后掃描函數(shù)的所有形參,之后會(huì)掃描foo函數(shù)內(nèi)所有的函數(shù)聲明,再掃描foo函數(shù)內(nèi)的變量聲明。之后該變量對(duì)象會(huì)加到Scope Chain中
確定this的值
此時(shí)Function Execution Context foo可以表示為
fooEC = { scopeChain: { pointer to fooEC.VO, foo.[[Scopes]] }, VO: { arguments: { 0: 66, length: 1 }, b: undefined, d: 50, bar: pointer to function bar(), }, this: { ... } }
接著進(jìn)入Function Execution Context foo的執(zhí)行階段
遇到賦值語(yǔ)句var b = 20;,于是fooEC .VO.b = 20
fooEC = { scopeChain: { pointer to fooEC.VO, foo.[[Scopes]] }, VO: { arguments: { 0: 66, length: 1 }, b: 20, d: 50, bar: pointer to function bar(), }, this: { ... } }
遇到bar函數(shù)定義語(yǔ)句,進(jìn)入bar函數(shù)的定義階段,bar的[[Scopes]]`屬性被確定
bar.[[Scopes]] = { pointer to fooEC.VO, pointer to globalEC.VO }
遇到bar()調(diào)用語(yǔ)句,進(jìn)入bar函數(shù)調(diào)用階段,此時(shí)進(jìn)入Function Execution Context bar的創(chuàng)建階段
創(chuàng)建Scope Chain(作用域鏈)
創(chuàng)建變量對(duì)象,此處為bar的活動(dòng)對(duì)象。先創(chuàng)建arguments對(duì)象,然后掃描函數(shù)的所有形參,之后會(huì)掃描foo函數(shù)內(nèi)所有的函數(shù)聲明,再掃描bar函數(shù)內(nèi)的變量聲明。之后該變量對(duì)象會(huì)加到Scope Chain中
確定this的值
此時(shí)Function Execution Context bar可以表示為
barEC = { scopeChain: { pointer to barEC.VO, bar.[[Scopes]] }, VO: { arguments: { length: 0 }, c: undefined }, this: { ... } }
接著進(jìn)入Function Execution Context bar的執(zhí)行階段
遇到賦值語(yǔ)句var c = 30,于是barEC.VO.c = 30;
barEC = { scopeChain: { pointer to barEC.VO, bar.[[Scopes]] }, VO: { arguments: { length: 0 }, c: 30 }, this: { ... } }
遇到打印語(yǔ)句console.log(a + b + c + d);,需要訪問(wèn)變量a,b,c,d
通過(guò)bar.[[Scopes]].globalEC.VO.a訪問(wèn)得到a=10
通過(guò)bar.[[Scopes]].fooEC.VO.b,bar.[[Scopes]].fooEC.VO.d訪問(wèn)得到b=20,d=50
通過(guò)barEC.VO.c訪問(wèn)得到c=30
通過(guò)運(yùn)算得出結(jié)果110
bar函數(shù)執(zhí)行完畢,Function Execution Context bar銷毀,變量c也隨之銷毀
foo函數(shù)執(zhí)行完畢,Function Execution Context foo銷毀,b,d,bar也隨之銷毀
所有代碼執(zhí)行完畢,等到該網(wǎng)頁(yè)被關(guān)閉或者瀏覽器被關(guān)閉,Global Execution Context才銷毀,a,foo才會(huì)銷毀
通過(guò)上面的例子,相信對(duì)Execution Context和作用域鏈的理解也更清楚了,下面簡(jiǎn)單總結(jié)一下作用域鏈:
作用域鏈的前端始終是當(dāng)前執(zhí)行的代碼所在Execution Context的變量對(duì)象;
下一個(gè)變量對(duì)象來(lái)自其包含Execution Context,以此類推;
最后一個(gè)變量對(duì)象始終是Global Execution Context的變量對(duì)象;
內(nèi)部Execution Context可通過(guò)作用域鏈訪問(wèn)外部Execution Context;反之不可以;
標(biāo)識(shí)符解析是沿著作用域鏈一級(jí)一級(jí)地搜索標(biāo)識(shí)符的過(guò)程。搜索過(guò)程始終從作用域鏈的前端開(kāi)始,然后逐級(jí)的向后回溯,直到找到標(biāo)識(shí)符為止(如果找不到,通常會(huì)導(dǎo)致錯(cuò)誤);
作用域鏈的本質(zhì)是一個(gè)指向變量對(duì)象的指針列表,只引用而不實(shí)際包含變量對(duì)象。
延長(zhǎng)作用域鏈下面兩種語(yǔ)句可以在作用域鏈的前端臨時(shí)增加一個(gè)變量對(duì)象以延長(zhǎng)作用域鏈,該變量對(duì)象會(huì)在代碼執(zhí)行后被移除
try-catch語(yǔ)句的catch塊
創(chuàng)建一個(gè)新的變量對(duì)象,其中包含的是被拋出的錯(cuò)誤對(duì)象的聲明
with語(yǔ)句
將指定的對(duì)象添加到作用域鏈中
function buildUrl(){ var qs = "?debug=true"; with(location){ var url = href + qs; } //console.log(href) 將會(huì)報(bào)href is not defined的錯(cuò)誤,因?yàn)閣ith語(yǔ)句執(zhí)行完with創(chuàng)建的變量對(duì)象就被移除了 return url; }
with語(yǔ)句接收window.location對(duì)象,因此其變量對(duì)象就包含了window.location對(duì)象的所有屬性,而這個(gè)變量對(duì)象被添加到作用域鏈的前端。所以在with語(yǔ)句里面使用href相當(dāng)于window.location.href。
解答問(wèn)題現(xiàn)在我們來(lái)解答最開(kāi)始的優(yōu)先級(jí)問(wèn)題
形參優(yōu)先級(jí)高于當(dāng)前函數(shù)名,低于內(nèi)部函數(shù)名
function fn(fn){ console.log(fn);// cc } fn("cc");
函數(shù)fn屬于Global Execution Context,而形參fn屬于Function Execution Context fn,此時(shí)作用域的前端是Function Execution Context fn的變量對(duì)象,所以console.log(fn)為形參的值
function fa(fb){ console.log(fb);// ? fb(){} function fb(){} console.log(fb);// ? fb(){} } fa("aaa");
調(diào)用fa函數(shù)時(shí),進(jìn)入Function Execution Context fa的創(chuàng)建階段,根據(jù)前面所說(shuō)的變量對(duì)象創(chuàng)建過(guò)程:
先創(chuàng)建arguments對(duì)象,然后掃描函數(shù)的所有形參,之后會(huì)掃描函數(shù)內(nèi)所有的函數(shù)聲明,再掃描函數(shù)內(nèi)的變量聲明;
掃描函數(shù)聲明時(shí),如果變量對(duì)象VO中已經(jīng)存在同名的屬性,則覆蓋這個(gè)屬性
我們可以得到fa的變量對(duì)象表示為:
fa.VO = { arguments: { 0:"aaa", length: 1 }, fb: pointer to function fb(), }
所以console.log(fb)得到的是fa.VO.fb的值? fb(){}
形參優(yōu)先級(jí)高于arguments
function fn(aa){ console.log(arguments);// Arguments?["hello world"] } fn("hello world"); function fn(arguments){ console.log(arguments);// hello world } fn("hello world");
調(diào)用fn函數(shù)時(shí),進(jìn)入Function Execution Context fn的創(chuàng)建階段,根據(jù)前面所說(shuō)的變量對(duì)象創(chuàng)建過(guò)程:
先創(chuàng)建arguments對(duì)象,然后掃描函數(shù)的所有形參,之后會(huì)掃描函數(shù)內(nèi)所有的函數(shù)聲明,再掃描函數(shù)內(nèi)的變量聲明;
先創(chuàng)建arguments對(duì)象,后掃描函數(shù)形參,如果形參名為arguments,將會(huì)覆蓋arguments對(duì)象
所以當(dāng)形參名為arguments時(shí),console.log(arguments)為形參的值hello world。
形參優(yōu)先級(jí)高于只聲明卻未賦值的局部變量,但是低于聲明且賦值的局部變量
function fa(aa){ console.log(aa);//aaaaa var aa; console.log(aa);//aaaaa } fa("aaaaa");
調(diào)用fa函數(shù)時(shí),進(jìn)入Function Execution Context fa的創(chuàng)建階段,根據(jù)前面所說(shuō)的變量對(duì)象創(chuàng)建過(guò)程:
先創(chuàng)建arguments對(duì)象,然后掃描函數(shù)的所有形參,之后會(huì)掃描函數(shù)內(nèi)所有的函數(shù)聲明,再掃描函數(shù)內(nèi)的變量聲明;
掃描函數(shù)內(nèi)的變量聲明時(shí),如果變量名與已經(jīng)聲明的形參或函數(shù)相同,此時(shí)什么都不會(huì)發(fā)生,變量聲明不會(huì)干擾已經(jīng)存在的這個(gè)同名屬性
所以創(chuàng)建階段之后Function Execution Context fa的變量對(duì)象表示為:
fa.VO = { arguments: { 0:"aaaaa", length: 1 }, aa:"aaaaa", }
之后進(jìn)入Function Execution Context fa的執(zhí)行階段:console.log(aa);打印出fa.VO.aa(形參aa)的值aaaaa;由于var aa;僅聲明而未賦值,所以不會(huì)改變fa.VO.aa的值,所以下一個(gè)console.log(aa);打印出的仍然是fa.VO.aa(形參aa)的值aaaaa。
function fb(bb){ console.log(bb);//bbbbb var bb = "BBBBB"; console.log(bb);//BBBBB } fb("bbbbb");
調(diào)用fb函數(shù)時(shí),進(jìn)入Function Execution Context fb的創(chuàng)建階段,根據(jù)前面所說(shuō)的變量對(duì)象創(chuàng)建過(guò)程:
先創(chuàng)建arguments對(duì)象,然后掃描函數(shù)的所有形參,之后會(huì)掃描函數(shù)內(nèi)所有的函數(shù)聲明,再掃描函數(shù)內(nèi)的變量聲明;
掃描函數(shù)內(nèi)的變量聲明時(shí),如果變量名與已經(jīng)聲明的形參或函數(shù)相同,此時(shí)什么都不會(huì)發(fā)生,變量聲明不會(huì)干擾已經(jīng)存在的這個(gè)同名屬性
所以創(chuàng)建階段之后Function Execution Context fb的變量對(duì)象表示為:
fb.VO = { arguments: { 0:"bbbbb", length: 1 }, bb:"bbbbb", }
之后進(jìn)入Function Execution Context fb的執(zhí)行階段:console.log(bb);打印出fb.VO.bb(形參bb)的值"bbbbb";遇到var bb = "BBBBB";,fb.VO.bb的值將被賦為BBBBB,所以下一個(gè)console.log(bb);打印出fb.VO.bb(局部變量bb)的值BBBBB。
函數(shù)和變量都會(huì)聲明提升,函數(shù)名和變量名同名時(shí),函數(shù)名的優(yōu)先級(jí)要高。
console.log(cc);//? cc(){} var cc = 1; function cc(){}
根據(jù)Global Execution Context的創(chuàng)建階段中創(chuàng)建變量對(duì)象的過(guò)程:是先掃描函數(shù)聲明,再掃描變量聲明,且變量聲明不會(huì)影響已存在的同名屬性。所以在遇到var cc = 1;這個(gè)聲明語(yǔ)句之前,global.VO.cc為? cc(){}。
執(zhí)行代碼時(shí),同名函數(shù)會(huì)覆蓋只聲明卻未賦值的變量,但是它不能覆蓋聲明且賦值的變量
var cc = 1; var dd; function cc(){} function dd(){} console.log(cc);//1 console.log(dd);//? dd(){}
Global Execution Context的創(chuàng)建階段之后,Global Execution Context的變量對(duì)象可以表示為:
global.VO = { cc:pointer to function cc(), dd:pointer to function dd() }
然后進(jìn)入Global Execution Context的執(zhí)行階段,遇到var cc = 1;這個(gè)聲明賦值語(yǔ)句后, global.VO.cc將被賦值為1;然后再遇到var dd這個(gè)聲明語(yǔ)句,由于僅聲明未賦值,所以不改變global.VO.dd的值;所以console.log(cc);打印出1,console.log(dd);打印出? dd(){}
局部變量也會(huì)聲明提升,可以先使用后聲明,不影響外部同名變量
每個(gè)Execution Context都會(huì)有變量創(chuàng)建這個(gè)過(guò)程,所以會(huì)有聲明提升;根據(jù)作用域鏈,如果局部變量與外部變量同名,那么最先找到的是局部變量,影響不到外部同名變量
相關(guān)資料JavaScript基礎(chǔ)系列---變量及其值類型
Understanding Scope in JavaScript
What is the Execution Context & Stack in JavaScript?
深入探討JavaScript的執(zhí)行環(huán)境和棧
作用域原理
JavaScript執(zhí)行環(huán)境 + 變量對(duì)象 + 作用域鏈 + 閉包
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/95064.html
摘要:之前一篇文章我們?cè)敿?xì)說(shuō)明了變量對(duì)象,而這里,我們將詳細(xì)說(shuō)明作用域鏈。而的作用域鏈,則同時(shí)包含了這三個(gè)變量對(duì)象,所以的執(zhí)行上下文可如下表示。下圖展示了閉包的作用域鏈。其中為當(dāng)前的函數(shù)調(diào)用棧,為當(dāng)前正在被執(zhí)行的函數(shù)的作用域鏈,為當(dāng)前的局部變量。 showImg(https://segmentfault.com/img/remote/1460000008329355);初學(xué)JavaScrip...
摘要:所以,有另一種說(shuō)法認(rèn)為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實(shí)體。所以本文中將以維基百科中的定義為準(zhǔn)即在計(jì)算機(jī)科學(xué)中,閉包,又稱詞法閉包或函數(shù)閉包,是引用了自由變量的函數(shù)。 閉包(closure)是JavaScript中一個(gè)神秘的概念,許多人都對(duì)它難以理解,我也一直處于似懂非懂的狀態(tài),前幾天深入了解了一下執(zhí)行環(huán)境以及作用域鏈,可戳查看詳情,而閉包與作用域及作用域鏈的關(guān)系密不可分,所...
摘要:所以覺(jué)得把這個(gè)執(zhí)行的詳細(xì)過(guò)程整理一下,幫助更好的理解。類似的語(yǔ)法報(bào)錯(cuò)的如下圖所示三預(yù)編譯階段代碼塊通過(guò)語(yǔ)法分析階段之后,語(yǔ)法都正確的下回進(jìn)入預(yù)編譯階段。另開(kāi)出新文章詳細(xì)分析,主要介紹執(zhí)行階段中的同步任務(wù)執(zhí)行和異步任務(wù)執(zhí)行機(jī)制事件循環(huán)。 一、概述 js是一種非常靈活的語(yǔ)言,理解js引擎的執(zhí)行過(guò)程對(duì)于我們學(xué)習(xí)js是非常有必要的??戳撕芏噙@方便文章,大多數(shù)是講的是事件循環(huán)(event loo...
摘要:變量對(duì)象作用域鏈因?yàn)樽兞繉?duì)象在執(zhí)行上下文進(jìn)入執(zhí)行階段時(shí),就變成了活動(dòng)對(duì)象,因此圖中使用了來(lái)表示。 作用域 作用域就是變量與函數(shù)的可訪問(wèn)范圍,即作用域控制著變量與函數(shù)的可見(jiàn)性和生命周期。在 JavaScript 中,變量的作用域有全局作用域和局部作用域兩種。JavaScript 采用詞法作用域(lexical scoping),也就是靜態(tài)作用域。 靜態(tài)作用域 函數(shù)的作用域在函數(shù)定義的時(shí)候...
摘要:變量對(duì)象作用域鏈因?yàn)樽兞繉?duì)象在執(zhí)行上下文進(jìn)入執(zhí)行階段時(shí),就變成了活動(dòng)對(duì)象,因此圖中使用了來(lái)表示。 作用域 作用域就是變量與函數(shù)的可訪問(wèn)范圍,即作用域控制著變量與函數(shù)的可見(jiàn)性和生命周期。在 JavaScript 中,變量的作用域有全局作用域和局部作用域兩種。JavaScript 采用詞法作用域(lexical scoping),也就是靜態(tài)作用域。 靜態(tài)作用域 函數(shù)的作用域在函數(shù)定義的時(shí)候...
閱讀 4113·2021-11-22 13:53
閱讀 3714·2021-11-19 11:29
閱讀 1481·2021-09-08 09:35
閱讀 3326·2020-12-03 17:26
閱讀 588·2019-08-29 16:06
閱讀 2222·2019-08-26 13:50
閱讀 1270·2019-08-23 18:32
閱讀 2228·2019-08-23 18:12