什么是作用域和執(zhí)行上下文

什么是作用域和執(zhí)行上下文

說到 Javascript 中的作用域蜕便,通常一同出現(xiàn)的還有一個執(zhí)行上下文(execution context)的概念,以前我在網(wǎng)上搜索相關(guān)的內(nèi)容總是理不清這兩者的關(guān)系谷异。似乎函數(shù),作用域锦聊,執(zhí)行上下文這三者天生就是糾纏在一起的歹嘹。為了獲得一手的資料我翻看了 ES6 規(guī)范,把他們到底是什么梳理了一下:

作用域

首先我們來說下作用域孔庭,簡單來說作用域就是一個區(qū)域尺上,包含了其中變量,常量圆到,函數(shù)等等定義信息和賦值信息怎抛,以及這個區(qū)域內(nèi)代碼書寫的結(jié)構(gòu)信息。作用域可以嵌套芽淡。我們通常知道 js 中函數(shù)的定義可以產(chǎn)生作用域抽诉,下面我們用具體代碼來示例下:


scopeexecctx1.png

全局作用域(global scope)里面定義了兩個變量,一個函數(shù)吐绵。walk 函數(shù)生成的作用域里面定義了一個變量迹淌,兩個函數(shù)。innerFunc 和 anotherInnerFunc 這兩個函數(shù)生成的作用域里面分別定義了一個變量己单。在規(guī)范中作用域更官方的叫法是詞法環(huán)境(Lexical Environments)唉窃。什么意思?就是作用域包含哪些內(nèi)容取決于你代碼怎么寫纹笼,你把定義 go 變量寫在了 walk 函數(shù)里面纹份,那么 go 變量就屬于 walk 函數(shù)作用域。

作用域其實由兩部分組成:

  1. 記錄作用域內(nèi)變量信息(我們假設(shè)變量,常量蔓涧,函數(shù)等統(tǒng)稱為變量)和代碼結(jié)構(gòu)信息的東西件已,稱之為 Environment Record。
  2. 一個引用 __outer__元暴,這個引用指向當(dāng)前作用域的父作用域篷扩。拿上面代碼為例。innerFunc 的函數(shù)作用域有一個引用指向 walk 函數(shù)作用域茉盏,walk 函數(shù)作用域有一個引用指向全局作用域鉴未。全局作用域的 __outer__ 為 null。
變量查找(ResolveBinding):

規(guī)范中定義了查找一個變量的過程:先查看當(dāng)前作用域里面的 Environment Record 是否有此變量的信息鸠姨,如果找到了铜秆,則返回當(dāng)前作用域內(nèi)的這個變量。如果沒有查找到讶迁,則順著 __outer__ 到父作用域里面的 Environment Record 查找连茧,以此遞歸。所以我們通常所說的函數(shù)內(nèi)同名變量遮蔽全局變量就是這么回事巍糯。不過如果你在變量查找的時候指定某個作用域中的 Environment Record梅屉,那么也是可以的,譬如:window.name 【其實 window 對象就是全局作用域的 Environment Record 對象鳞贷,但是普通函數(shù)作用域的 Environment Record 對象是獲取不到的】坯汤。

生成作用域的語法:
  1. 函數(shù)聲明
function f() {
  var inner = 'inner';
  console.log( inner );
}
f(); // inner;
console.log( inner ); // Uncaught ReferenceError: inner is not defined
  1. catch 語句
try {
  throw new Error( 'customized error' );
} catch( err ) {
  var iamnoterror = 'not error';
  console.log( iamnoterror ); // not error
  console.log( err ); // Error: customized error
}
console.log( iamnoterror ); // not error
console.log( err ); // Uncaught ReferenceError: e is not defined

這里特別指出的是 catch 語句生成的作用域只會框住參數(shù)部分的變量(err),使其不能在外面訪問搀愧。對于 catch 語句體里面聲明的變量并不起作用惰聂。我們看規(guī)范里面怎么說:

For each element argName of the BoundNames of CatchParameter, do
a. Perform catchEnv.CreateMutableBinding(argName).

catchEvn 就是 catch 語句生成的作用域,但是這個作用域只保存參數(shù)列表中的變量(CreateMutableBinding(argName))咱筛。

  1. 語句塊
if ( true ) {
  let bv = 'bv';
  const B_C = 'BC';
  let blockFunc = function() {}
  function notBlockFunc() {}

  console.log( bv ); // bv
  console.log( B_C ); // BC
  console.log( notBlockFunc ); // function notBlockFunc() {}
  console.log( blockFunc ); // function () {}
  
}
console.log( bv ); // Uncaught ReferenceError: bv is not defined
console.log( B_C ); // Uncaught ReferenceError: B_C is not defined
console.log( notBlockFunc ); // function notBlockFunc() {}
console.log( blockFunc ); // ReferenceError: blockFunc is not defined

語句塊 {} 會生成一個新的作用域搓幌,但是這個作用域只綁定塊級變量,常量等迅箩,即 let溉愁,const 聲明的屬于塊級作用域,而 var 聲明的還是屬于塊級作用域的父作用域饲趋。

執(zhí)行上下文

接下來我們說下執(zhí)行上下文(execution context)拐揭,執(zhí)行上下文是用于跟蹤代碼的運行情況,其特征如下:

  1. 一段代碼塊對應(yīng)一個執(zhí)行上下文奕塑,被封裝成函數(shù)的代碼被視作一段代碼塊堂污,或者“全局作用域”也被視作一段代碼塊。
  2. 當(dāng)程序運行龄砰,進(jìn)入到某段代碼塊時盟猖,一個新的執(zhí)行上下文被創(chuàng)建讨衣,并被放入一個 stack 中。當(dāng)程序運行到這段代碼塊結(jié)尾后式镐,對應(yīng)的執(zhí)行上下文被彈出 stack反镇。
  3. 當(dāng)程序在某段代碼塊中運行到某個點需要轉(zhuǎn)到了另一個代碼塊時(調(diào)用了另一個函數(shù)),那么當(dāng)前的可執(zhí)行上下文的狀態(tài)會被置為掛起娘汞,然后生成一個新的可執(zhí)行上下文放入 stack 的頂部歹茶。
  4. stack 最頂部的可執(zhí)行上下文被稱為 running execution context。當(dāng)頂部的可執(zhí)行上下文被彈出后价说,上一個掛起的可執(zhí)行上下文繼續(xù)執(zhí)行辆亏。

我們用代碼來示例下(從 outer 調(diào)用到 level1 調(diào)用风秤,再逐層返回):


scopeexecctx2.png

執(zhí)行上下文對象的內(nèi)部屬性:

  • [[code evaluation]]:當(dāng)前代碼塊執(zhí)行的狀態(tài):prerform鳖目,suspend,resume缤弦。
  • [[Function]]:如果當(dāng)前執(zhí)行上下文對應(yīng)的是一個函數(shù)领迈,那么這個屬性就保存的這個函數(shù)對象。如果對應(yīng)的是全局環(huán)境(可以是一個 script 或者 module)碍沐,屬性值是 null狸捅。
  • [[Real]]:類似與沙箱的概念?(我還沒有看懂累提,不過不太影響此篇的內(nèi)容)尘喝。

如果程序執(zhí)行到某個點拋出異常了,那么我們可以用這個記錄執(zhí)行上下文的 stack 來追蹤到底哪里出錯了斋陪,可以看到整個調(diào)用棧朽褪,此時內(nèi)部屬性 [[Function]] 就起到作用了:

scopeexecctx3.png

作用域和執(zhí)行上下文的關(guān)系

其實大家看下作用域和執(zhí)行上下文各自的職責(zé),你會發(fā)現(xiàn)他們幾乎是沒有啥交集的无虚。那么為啥通常兩者會被同時提到呢缔赠?因為在一個函數(shù)被執(zhí)行時,創(chuàng)建的執(zhí)行上下文對象除了保存了些代碼執(zhí)行的信息友题,還會把當(dāng)前的作用域保存在執(zhí)行上下文中嗤堰。所以它們的關(guān)系只是存儲關(guān)系。

再談變量查找(ResolveBinding):

結(jié)合作用域和執(zhí)行上下文度宦,我們再來看下變量查找的過程踢匣。其實第一步不是到作用域里面找 Environment Record,而是先從當(dāng)前的執(zhí)行上下文中找保存的作用域(對象)戈抄,然后再是通過作用域鏈向上查找變量符糊。而且同一個執(zhí)行上下文保存的作用域(對象)是可變的,當(dāng)代碼在同一個執(zhí)行上下文中執(zhí)行的時候呛凶,如果碰到有必要生成一個新作用域的時候男娄,這個新的作用域會被添加到作用域鏈的頭部,然后執(zhí)行上下文就保存的作用域?qū)ο缶透鲁蛇@個新的作用域。等這個新的作用域生命周期完成后模闲,作用域鏈又會恢復(fù)到之前的狀況建瘫,然后執(zhí)行上下文保存的作用域也會恢復(fù)成之前的。示例:

scopeexecctx4.png

this

稍微提下尸折,我看到網(wǎng)上有把執(zhí)行上下文等同于 this 的文章啰脚,其實 this 的值是通過當(dāng)前執(zhí)行上下文中保存的作用域(對象)來獲取到的,規(guī)范如下实夹。

ResolveThisBinding ( )
The abstract operation ResolveThisBinding determines the binding of the keyword this using the LexicalEnvironment of the running execution context. ResolveThisBinding performs the following steps:

  1. Let envRec be GetThisEnvironment( ).
  2. Return envRec.GetThisBinding().

我接下來會要總結(jié)函數(shù)作為普通函數(shù)和作為構(gòu)造函數(shù)被調(diào)用時的區(qū)別橄浓,那個時候應(yīng)該會對 this 有更深入的解釋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亮航,一起剝皮案震驚了整個濱河市荸实,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌缴淋,老刑警劉巖准给,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異重抖,居然都是意外死亡露氮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門钟沛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來畔规,“玉大人,你說我怎么就攤上這事恨统∪ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵延欠,是天一觀的道長陌兑。 經(jīng)常有香客問我,道長由捎,這世上最難降的妖魔是什么兔综? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮狞玛,結(jié)果婚禮上软驰,老公的妹妹穿的比我還像新娘。我一直安慰自己心肪,他們只是感情好锭亏,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著硬鞍,像睡著了一般慧瘤。 火紅的嫁衣襯著肌膚如雪戴已。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天锅减,我揣著相機與錄音糖儡,去河邊找鬼。 笑死怔匣,一個胖子當(dāng)著我的面吹牛握联,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播每瞒,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼金闽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了剿骨?” 一聲冷哼從身側(cè)響起代芜,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎懦砂,沒想到半個月后蜒犯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體组橄,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡荞膘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了玉工。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片羽资。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖遵班,靈堂內(nèi)的尸體忽然破棺而出屠升,到底是詐尸還是另有隱情,我是刑警寧澤狭郑,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布腹暖,位于F島的核電站,受9級特大地震影響翰萨,放射性物質(zhì)發(fā)生泄漏脏答。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一亩鬼、第九天 我趴在偏房一處隱蔽的房頂上張望殖告。 院中可真熱鬧,春花似錦雳锋、人聲如沸黄绩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爽丹。三九已至筑煮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間粤蝎,已是汗流浹背咆瘟。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留诽里,地道東北人袒餐。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像谤狡,于是被迫代替她去往敵國和親灸眼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容