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