摘要:執(zhí)行上下文作用域鏈和內(nèi)部機(jī)制一執(zhí)行上下文執(zhí)行上下文是代碼的執(zhí)行環(huán)境,它包括的值變量對(duì)象和函數(shù)。創(chuàng)建作用域鏈一旦可變對(duì)象創(chuàng)建完,引擎就開(kāi)始初始化作用域鏈。
執(zhí)行上下文、作用域鏈和JS內(nèi)部機(jī)制(Execution context, Scope chain and JavaScript internals)
一、執(zhí)行上下文執(zhí)行上下文(Execution context EC)是js代碼的執(zhí)行環(huán)境,它包括this的值、變量、對(duì)象和函數(shù)。js執(zhí)行上下文有3種類型
全局上下文是文件第一次加載到瀏覽器,js代碼開(kāi)始執(zhí)行的默認(rèn)執(zhí)行上下文。在瀏覽器環(huán)境中,嚴(yán)格模式下this的值為undefined,否則this的值為window對(duì)象。GEC只能有一個(gè)(因?yàn)閖s執(zhí)行的全局環(huán)境只能有一個(gè))。
函數(shù)執(zhí)行時(shí)創(chuàng)建函數(shù)執(zhí)行上下文,每個(gè)函數(shù)都有自己的執(zhí)行上下文。FEC可以獲取到GEC中的內(nèi)容。當(dāng)在全局上下文中執(zhí)行代碼時(shí)js引擎發(fā)現(xiàn)一個(gè)函數(shù)調(diào)用,則創(chuàng)建一個(gè)函數(shù)執(zhí)行上下文。
執(zhí)行eval時(shí)創(chuàng)建
二、執(zhí)行上下文棧執(zhí)行上下文棧Execution context stack (ECS)是執(zhí)行js代碼時(shí)創(chuàng)建的執(zhí)行棧結(jié)構(gòu)。GEC默認(rèn)在棧的最里層,當(dāng)js引擎發(fā)現(xiàn)一個(gè)函數(shù)調(diào)用,則創(chuàng)建這個(gè)函數(shù)的FEC并push進(jìn)棧,js引擎執(zhí)行棧頂上下文關(guān)聯(lián)的函數(shù),一旦函數(shù)執(zhí)行完,則將其FEC pop出棧,并往下執(zhí)行。
看個(gè)例子(動(dòng)圖插不了棧動(dòng)圖鏈接)
var a = 10; function functionA() { console.log("Start function A"); function functionB(){ console.log("In function B"); } functionB(); } functionA(); console.log("GlobalContext");
當(dāng)上面的代碼在瀏覽器中加載時(shí),js引擎先將GEC push入ECS中,當(dāng)在GEC中調(diào)用functionA時(shí),functionA執(zhí)行上下文被push入棧,并開(kāi)始執(zhí)行functionA。
當(dāng)functionB在functionA中被調(diào)用時(shí),functionB的執(zhí)行上下文被push入棧,開(kāi)始執(zhí)行functionB,當(dāng)functionB中內(nèi)容執(zhí)行完,functionB執(zhí)行上下文被pop出棧,此時(shí)棧頂為functionA的執(zhí)行上下文,繼續(xù)執(zhí)行functionA的代碼,執(zhí)行完后pop出棧,棧頂為GEC。
最終執(zhí)行GEC中代碼,執(zhí)行完pop整個(gè)代碼結(jié)束。
上面討論了js引擎如何處理執(zhí)行上下文(push和pop),下面討論js引擎如何創(chuàng)建執(zhí)行上下文,這個(gè)過(guò)程分為兩個(gè)階段:創(chuàng)建階段和執(zhí)行階段。
三、創(chuàng)建執(zhí)行上下文 1. 創(chuàng)建階段(后面又叫編譯階段)js引擎調(diào)用函數(shù),但函數(shù)還沒(méi)開(kāi)始執(zhí)行階段。
js引擎在這個(gè)階段對(duì)整個(gè)函數(shù)進(jìn)行一個(gè)編譯(compile the code),主要干了下面三件事:
可變對(duì)象是包含所有變量、函數(shù)參數(shù)和內(nèi)部函數(shù)聲明信息的特殊對(duì)象,它是一個(gè)特殊對(duì)象且沒(méi)有__proto__屬性。
一旦可變對(duì)象創(chuàng)建完,js引擎就開(kāi)始初始化作用域鏈。作用域鏈?zhǔn)且粋€(gè)當(dāng)前函數(shù)所在的可變對(duì)象的列表,其中包括GEC的可變對(duì)象和當(dāng)前函數(shù)的可變對(duì)象。
初始化this的值
下面通過(guò)一個(gè)例子進(jìn)行說(shuō)明
function funA (a, b) { var c = 3; var d = 2; d = function() { return a - b; } } funA(3, 2);
當(dāng)調(diào)用funA和執(zhí)行funA前的這段時(shí)間,js引擎為funA創(chuàng)建了一個(gè)executionContextObj如下
executionContextObj = { variableObject: {}, // All the variable, arguments and inner function details of the funA scopechain: [], // List of all the scopes inside which the current function is this // Value of this }
可變對(duì)象包含參數(shù)對(duì)象(包含函數(shù)參數(shù)的細(xì)節(jié)),聲明的變量和函數(shù),如下所示
variableObject = { argumentObject : { 0: a, 1: b, length: 2 }, a: 3, b: 2 c: undefined, d: undefined then pointer to the function defintion of d }
argumentObject如上所示
函數(shù)中的變量會(huì)被初始為undefined,參數(shù)也會(huì)在可變對(duì)象中呈現(xiàn)
如果變量在參數(shù)對(duì)象中已存在,js引擎選擇忽略
js引擎在當(dāng)前函數(shù)中遇到函數(shù)定義,會(huì)用函數(shù)名創(chuàng)建一個(gè)屬性指向函數(shù)定義存儲(chǔ)的堆內(nèi)容
2. 執(zhí)行階段在此階段,js引擎會(huì)重掃一遍函數(shù),用具體的變量的值來(lái)更新可變對(duì)象,并執(zhí)行代碼內(nèi)容。
執(zhí)行階段執(zhí)行完后,可變對(duì)象的值如下:
variableObject = { argumentObject : { 0: a, 1: b, length: 2 }, a: 3, b: 2, c: 3, d: undefined then pointer to the function defintion of d }四、完整的例子
代碼如下
a = 1; var b = 2; cFunc = function(e) { var c = 10; var d = 15; a = 3 function dFunc() { var f = 5; } dFunc(); } cFunc(10);全局編譯階段
當(dāng)瀏覽器加載上面的代碼后,js引擎進(jìn)入編譯階段,只處理聲明,不處理值。下面走讀一遍代碼:
a被賦值1,但它并不是個(gè)變量或函數(shù)聲明,js引擎在編譯階段什么都不做;
b變量聲明初始化為undefined;
cFunc函數(shù)聲明初始化為undefined。
此時(shí)的
globalExecutionContextObj = { variableObject: { // 原文中有時(shí)用activationObj argumentObj : { length:0 }, b: undefined, cFunc: Pointer to the function definition }, scopeChain: [GLobal execution context variable object], this: value of this }全局執(zhí)行階段
再接著上面,js引擎進(jìn)入執(zhí)行階段并再過(guò)一遍。此時(shí)將會(huì)更新變量名和執(zhí)行
js引擎發(fā)現(xiàn)可變對(duì)象中沒(méi)有a屬性,因此在GEC中添加a屬性,并初始化為1;
可變對(duì)象有b,直接更新b的值為2;
接著是函數(shù)聲明,不做任何事;
最后調(diào)用cFunc,js引擎再次進(jìn)入編譯階段創(chuàng)建一個(gè)cFunc的執(zhí)行上下文。
此時(shí)
globalExecutionContextObj = { variableObject: { argumentObj : { length:0 }, b: 2, cFunc: Pointer to the function definition, a: 1 }, scopeChain: [GLobal execution context variable object], this: value of this }cFunc的編譯階段
由于cFunc有個(gè)參數(shù)e,js引擎會(huì)在cFunc執(zhí)行上下文對(duì)象可變對(duì)象添加e屬性,并初始化為2
js引擎查看cFunc執(zhí)行上下文的可變對(duì)象沒(méi)有c,因此添加c,并初始化為undefined,d類似;
a = 3非聲明,跳過(guò);
函數(shù)聲明,創(chuàng)建dFunc屬性指向函數(shù)的堆空間;
對(duì)dFunc執(zhí)行語(yǔ)句忽略
此時(shí)
cFuncExecutionContextObj = { activationbj: { argumentObj : { 0: e, length:1 }, e: 10, c: undefined, d: undefined dFunc: Pointer to the function definition, }, scopeChain: [cFunc variable object, Global exection context variable object], this: value of this }cFunc的執(zhí)行階段
c和d獲取到初始化值;
a不是cFunc執(zhí)行上下文對(duì)象中的屬性,js引擎會(huì)在作用率鏈的幫助下轉(zhuǎn)到GEC(全局執(zhí)行上下文),查找a是否在GEC中。如果不存在,則會(huì)在當(dāng)前作用域創(chuàng)建并初始化它;如果GEC中有,則更新其值,這里會(huì)更新為3。js引擎只有在發(fā)現(xiàn)一個(gè)變量在當(dāng)前執(zhí)行上下文對(duì)象屬性中找不到時(shí)會(huì)跳轉(zhuǎn)到GEC中;
創(chuàng)建dFunc屬性并指向函數(shù)的堆內(nèi)存
cFuncExecutionContextObj = { activationbj: { argumentObj : { 0: e, length:1 }, e: 10, c: 10, d: 15 dFunc: Pointer to the function definition, }, scopeChain: [cFunc variable object, Global exection context variable object], this: value of this }
調(diào)用dFunc,js引擎再次進(jìn)入編譯階段,創(chuàng)建dFunc執(zhí)行上下文對(duì)象。
dFunc執(zhí)行上下文對(duì)象可以訪問(wèn)到cFunc和全局作用域中的所有變量和函數(shù);同樣cFunc可以訪問(wèn)到全局的,但不能訪問(wèn)dFunc中的;全局上下文對(duì)象不能訪問(wèn)cFunc和dFunc中的變量和對(duì)象。
有了上面的概念,對(duì)hoisting(變量提升)應(yīng)該更容易理解了。
作用域鏈?zhǔn)钱?dāng)前函數(shù)所在的可變對(duì)象列表
看下面一段代碼
a = 1; var b = 2; cFunc = function(e) { var c = 10; var d = 15; console.log(c); console.log(a); function dFunc() { var f = 5; console.log(f) console.log(c); console.log(a); } dFunc(); } cFunc(10);
當(dāng)cFunc被調(diào)用時(shí),cFunc的作用域鏈如下
Scope chain of cFunc = [ cFunc variable object, Global Execution Context variable object]
當(dāng)dFunc被調(diào)用時(shí),dFunc在cFunc中,dFunc的作用域鏈包含dFunc、cFunc和全局可變對(duì)象
Scope chain of dFunc = [dFunc variable object, cFunc variable object, Global execution context variable object]
當(dāng)我們嘗試訪問(wèn)dFunc中的f,js引擎查看f是否可從dFunc的可變對(duì)象中獲取,找到console輸出;
訪問(wèn)c變量,js引擎首先在dFunc的可變對(duì)象中獲取,不能獲取,則到cFunc的可變對(duì)象中去獲取,找到console輸出;
訪問(wèn)a變量,同上,最后找到GEC的可變對(duì)象,獲取到并console輸出
同樣,cFunc中獲取c和a類似
在cFunc中訪問(wèn)不到f變量,但dFunc中可以通過(guò)作用域鏈獲取到c和d
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/97581.html
摘要:前言這段時(shí)間一直在消化作用域鏈和閉包的相關(guān)知識(shí)。而作用域鏈則是這套規(guī)則這套規(guī)則的具體運(yùn)行。是變量對(duì)象的縮寫(xiě)那這樣放有什么好處呢我們知道作用域鏈保證了當(dāng)前執(zhí)行環(huán)境對(duì)符合訪問(wèn)權(quán)限的變量和函數(shù)的有序訪問(wèn)。 前言:這段時(shí)間一直在消化作用域鏈和閉包的相關(guān)知識(shí)。之前看《JS高程》和一些技術(shù)博客,對(duì)于這些概念的論述多多少少不太清楚或者不太完整,包括一些大神的技術(shù)文章。這也給我的學(xué)習(xí)上造成了一些困惑,...
摘要:本期推薦文章從作用域鏈談閉包,由于微信不能訪問(wèn)外鏈,點(diǎn)擊閱讀原文就可以啦。推薦理由這是一篇譯文,深入淺出圖解作用域鏈,一步步深入介紹閉包。作用域鏈的頂端是全局對(duì)象,在全局環(huán)境中定義的變量就會(huì)綁定到全局對(duì)象中。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周開(kāi)始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第6天。 本...
摘要:首先,在創(chuàng)建函數(shù)時(shí),作用域鏈內(nèi)就會(huì)先填入對(duì)象,圖片只例舉了全部變量中的一部分。然后,解釋器進(jìn)入函數(shù)的執(zhí)行環(huán)境,同樣的,首先填入父級(jí)的作用域鏈,就是的,包括了對(duì)象活動(dòng)對(duì)象。之后再把的活動(dòng)對(duì)象填入到作用域鏈最頂部,這就是的作用域鏈了。 之前學(xué)習(xí)JS函數(shù)部分時(shí),提到了作用域這一節(jié),但是因?yàn)槭褂貌牧蠒?shū)不同,今天在讀博客的時(shí)候發(fā)現(xiàn)其實(shí)還有一個(gè)知識(shí)點(diǎn)即作用域鏈,所以來(lái)寫(xiě)一些個(gè)人理解和認(rèn)識(shí)加深記憶。...
摘要:閉包面試題解由于作用域鏈機(jī)制的影響,閉包只能取得內(nèi)部函數(shù)的最后一個(gè)值,這引起的一個(gè)副作用就是如果內(nèi)部函數(shù)在一個(gè)循環(huán)中,那么變量的值始終為最后一個(gè)值。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第8天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了...
摘要:使用上一篇文章的例子來(lái)說(shuō)明下自由變量進(jìn)階期深入淺出圖解作用域鏈和閉包訪問(wèn)外部的今天是今天是其中既不是參數(shù),也不是局部變量,所以是自由變量。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第7天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了解本進(jìn)階計(jì)...
閱讀 3155·2021-10-13 09:39
閱讀 2763·2021-09-27 13:34
閱讀 2119·2019-08-30 15:55
閱讀 3315·2019-08-30 15:43
閱讀 3714·2019-08-30 11:16
閱讀 1839·2019-08-26 18:28
閱讀 1419·2019-08-26 13:56
閱讀 1012·2019-08-26 13:35