本篇文章帶大家聊聊Javascript中的執(zhí)行上下文,分享一個思考題,通過對思考題的分析,想必會對執(zhí)行上下文有更加深入的理解。
在前面的幾篇文章中,我們深入了解了關于執(zhí)行上下文的三個重要成員:變量對象、作用域鏈和 this ,本篇文章是前四篇文章的的內(nèi)容的集合,聚合分散的知識點,做一個簡單的鞏固。不知道有沒有人是上一篇來的,我們的上一篇留下了一個思考題,通過對思考題的分析,想必會對執(zhí)行上下文有更加深入的理解。
思考題
這里為了稍微將案例復雜化一點,做了一點點修改,但是并沒有改變原題所考察的點。
function func(value){ getValue = function(){ console.log(value); }; return this } function getValue(){ console.log(5); } Func(1).getValue(); //為什么是1呢?
具體執(zhí)行分析
執(zhí)行全局代碼,創(chuàng)建全局執(zhí)行上下文,全局上下文被壓入執(zhí)行上下文棧
ECStack = [ globalContext ];
初始化全局上下文
globalContext = { VO: { func: reference to function func(){}, getValue: reference to function getValue(){} }, Scope: [globalContext.VO], this: globalContext.VO //全局上下文 }
初始化全局上下文同時創(chuàng)建了兩個函數(shù),因此也會保存他們父級作用域鏈在他們的內(nèi)部屬性 [[scope]] 內(nèi)
func.[[scope]] = [ globalContext.VO ]; getValue.[[scope]] = [ globalContext.VO ];
此時開始執(zhí)行代碼,執(zhí)行到最后的語句時先執(zhí)行 func 函數(shù),也就創(chuàng)建按步驟 func 函數(shù)執(zhí)行上下文:
-
復制函數(shù) [[scope]] 屬性創(chuàng)建作用域鏈
-
用 arguments 創(chuàng)建活動對象
-
初始化活動對象
-
將活動對象壓入 checksfunccope 作用域鏈頂端。
-
創(chuàng)建this,簡單分析:MemberExpression 值為func,func是一個函數(shù)對象,理所當然是一個Reference ,其中它的 base value 是 EnvironmentRecord ,所以它的 this 值為 ImplicitThisValue(ref),返回值始終是 undefined ,非嚴格模式下,其值會被隱式轉換為全局對象。
funcContext = { AO: { arguments: { // 數(shù)組 0: 1, length: 1 } }, Scope: [AO, globalContext.VO], this: undefined }
可能有人會有疑問,func 里的 getValue 呢?,因為它并沒有變量申明,因此他其實是一個屬性的賦值操作,在后面運行時才會被執(zhí)行。
創(chuàng)建函數(shù)執(zhí)行上下文后壓入執(zhí)行上下文棧
ECStack = [ funcContext, globalContext ];
函數(shù)開始執(zhí)行,此時就是為什么最后輸出是1的關鍵了,第一句賦值操作,那么就需要沿著執(zhí)行上下文去找變量 getValue,那么我們就來看 funcContext 中的作用域,首先找到 funcContext.AO 顯然并不存在 getValue 這一屬性,那么沿著作用域鏈往上找,找到了globalContext.VO ,找到了 getValue ,這時候就會給全局作用域下的 getValue 屬性重新賦值,賦的是一個函數(shù)的傳新版本,也就重新創(chuàng)建了函數(shù)作用域,將這個全新的 getValue 函數(shù)的父級作用域鏈保存在它在他們的內(nèi)部屬性 [[scope]] 內(nèi):
getValue .[[scope]] = [ funcContext.AO, globalContext.VO ];
然后才繼續(xù)返回 this ,查找 funcContext 的 this ,即返回undefined;func 執(zhí)行上下文出棧
ECStack = [ globalContext ];
繼續(xù)執(zhí)行Func(1).getValue()
,前半部分返回了 undefined ,此時系統(tǒng)隱式轉換為全局變量對象,從全局變量對象中找到 getValue 屬性。這時候我們發(fā)現(xiàn) getValue 早已不是當年那個少年,執(zhí)行全新的 getValue 的函數(shù)執(zhí)行上下文并入棧:
getValueContext = { AO: { arguments: { // 數(shù)組 length: 0 } }, Scope: [ AO, funcContext.AO, globalContext.VO ], this: undefined } ECStack = [ getValueContext, globalContext ];
函數(shù)開始執(zhí)行,發(fā)現(xiàn)她要輸出 value
,沿著作用域去找,getValueContext.AO 中并沒有這個屬性, 繼續(xù)往下找找到 funcContext.AO(注意! ),在形參中 找到了 value 那么就輸出對樣的值,也就輸出了1。
函數(shù)執(zhí)行完畢,getValueContext 和 globalContext 相繼出棧并銷毀,代碼運行完畢。
總結
本片以一個簡單但又不簡單的示例,將前面的四篇文章串聯(lián)起來,完整地分析了JS代碼執(zhí)行時執(zhí)行上下文的工作過程,希望大家對此能有更深的理解。但是,不知道有沒有細心的同學發(fā)現(xiàn),上面的示例中,執(zhí)行 getValue 函數(shù)的過程中,由尋找屬性 value的步驟(標記位置),那個時候 func 函數(shù)明明已經(jīng)執(zhí)行完畢了,他的執(zhí)行上下文已經(jīng)出棧了,為什么還能從他的執(zhí)行上下文中找到 value 屬性呢?這其實就是閉包產(chǎn)生的原理了,下一篇我們?nèi)匀挥眠@個示例去學習閉包產(chǎn)生的原理。
【推薦學習:javascript高級教程】