---只有程序員才懂的幽默---
一程序員去面試史简,面試官問:“你畢業(yè)才兩面乃秀,這三年工作經(jīng)驗(yàn)是怎么來的肛著?!”程序員回答:“加班跺讯∈嗷撸”
最近一段時間呢,被一個JS面試題刷屏了刀脏,接下來我們也簡單談一下這道JS面試題所引發(fā)的思考局荚。
題目是這樣的:
//比較下面兩段代碼,試述兩段代碼的不同之處
// A--------------------------
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
// B---------------------------
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
首先A愈污、B兩段代碼輸出返回的都是 “l(fā)ocal scope”耀态,如果對這一點(diǎn)還有疑問的同學(xué)請自覺回去溫習(xí)一下js作用域的相關(guān)知識。
接下來我們分析下這兩段代碼的執(zhí)行過程:
首先是A:
進(jìn)入全局環(huán)境上下文暂雹,全局環(huán)境被壓入環(huán)境棧首装,contextStack = [globalContext]
全局上下文環(huán)境初始化,
globalContext={
variable object:[scope, checkscope],
scope chain: variable object // 全局作用域鏈
}
,同時checkscope函數(shù)被創(chuàng)建杭跪,此時 checkscope.[[Scope]] = globalContext.scopeChain
執(zhí)行checkscope函數(shù)仙逻,進(jìn)入checkscope函數(shù)上下文,checkscope被壓入環(huán)境棧涧尿,contextStack=[checkscopeContext, globalContext]系奉。隨后checkscope上下文被初始化,它會復(fù)制checkscope函數(shù)的[[Scope]]變量構(gòu)建作用域,即 checkscopeContext={ scopeChain : [checkscope.[[Scope]]] }
checkscope的活動對象被創(chuàng)建 此時 checkscope.activationObject = [arguments], 隨后活動對象被當(dāng)做變量對象用于初始化现斋,checkscope.variableObject = checkscope.activationObject = [arguments, scope, f]喜最,隨后變量對象被壓入checkscope作用域鏈前端,(checckscope.scopeChain = [checkscope.variableObject, checkscope.[[Scope]] ]) == [[arguments, scope, f], globalContext.scopeChain]
函數(shù)f被初始化庄蹋,f.[[Scope]] = checkscope.scopeChain。
checkscope執(zhí)行流繼續(xù)往下走到 return f()迷雪,進(jìn)入函數(shù)f執(zhí)行上下文限书。函數(shù)f執(zhí)行上下文被壓入環(huán)境棧,contextStack = [fContext, checkscopeContext, globalContext]章咧。函數(shù)f重復(fù) 第4步 動作倦西。最后 f.scopeChain = [f.variableObject,checkscope.scopeChain]
函數(shù)f執(zhí)行完畢,f的上下文從環(huán)境棧中彈出赁严,此時 contextStack = [checkscopeContext, globalContext]扰柠。同時返回 scope, 解釋器根據(jù)f.scopeChain查找變量scope,在checkscope.scopeChain中找到scope(local scope)。
checkscope函數(shù)執(zhí)行完畢疼约,其上下文從環(huán)境棧中彈出卤档,contextStack = [globalContext]
如果你理解了A的執(zhí)行流程,那么B的流程在細(xì)節(jié)上一致程剥,唯一的區(qū)別在于B的環(huán)境棧變化不一樣劝枣,
A: contextStack = [globalContext] —> contextStack = [checkscopeContext, globalContext] —> contextStack = [fContext, checkscopeContext, globalContext] —> contextStack = [checkscopeContext, globalContext] —> contextStack = [globalContext]
B: contextStack = [globalContext] —> contextStack = [checkscopeContext, globalContext] —> contextStack = [fContext, globalContext] —> contextStack = [globalContext]
也就是說,真要說這兩段代碼有啥不同,那就是他們執(zhí)行過程中環(huán)境棧的變化不一樣舔腾,其他的兩種方式都一樣溪胶。
其實(shí)對于理解這兩段代碼而言最根本的一點(diǎn)在于,javascript是使用靜態(tài)作用域的語言稳诚,他的作用域在函數(shù)創(chuàng)建的時候便已經(jīng)確定(不含arguments)哗脖。
說了這么一大坨偏理論的東西,能堅(jiān)持看下來的同學(xué)估計(jì)都要睡著了…是的扳还,這么一套理論性的東西糾結(jié)有什么用呢才避,我只要知道函數(shù)作用域在創(chuàng)建時便已經(jīng)生成不就好了么。沒有實(shí)踐價值的理論往往得不到重視普办。那我們來看看工扎,當(dāng)我們了解到這一套理論之后我們的世界到底會發(fā)生了什么變化:
這樣一段代碼:
function setFirstName(firstName){
return function(lastName){
return firstName+" "+lastName;
}
}
var setLastName = setFirstName("kuitos");
var name = setLastName("lau");
// 調(diào)用setFirstName函數(shù)時返回一個匿名函數(shù),該匿名函數(shù)會持有setFirstName函數(shù)作用域的變量對象(里面包含arguments和firstName)衔蹲,不管匿名函數(shù)是否會使用該變量對象里的信息肢娘,這個持有邏輯均不會改變。
// 也就是當(dāng)setFirstName函數(shù)執(zhí)行完之后其執(zhí)行環(huán)境被銷毀舆驶,但是他的變量對象會一直保存在內(nèi)存中不被銷毀(因?yàn)楸荒涿瘮?shù)hold)橱健。同樣的,垃圾回收機(jī)制會因?yàn)樽兞繉ο蟊灰恢県old而不做回收處理沙廉。這個時候內(nèi)存泄露就發(fā)生了拘荡。這時候我們需要做手動釋放內(nèi)存的處理。like this:
setLastName = null;
// 由于匿名函數(shù)的引用被置為null撬陵,那么其hold的setFirstName的活動對象就能被安全回收了珊皿。
// 當(dāng)然,現(xiàn)代瀏覽器引擎(以V8為首)都會嘗試回收閉包所占用的內(nèi)存巨税,所以這一點(diǎn)我們也不必過多處理蟋定。
2016-11-30 00:58:53