【轉(zhuǎn)載】JS進階之---執(zhí)行上下文,變量對象,變量提升

原文地址:https://www.cnblogs.com/lishuxue/p/6558788.html

一、結構順序大體介紹

JavaScript代碼的整個執(zhí)行過程,分為兩個階段,代碼編譯階段與代碼執(zhí)行階段。

編譯階段由編譯器完成,將代碼翻譯成可執(zhí)行代碼,這個階段作用域規(guī)則會確定。

執(zhí)行階段由引擎完成,主要任務是執(zhí)行可執(zhí)行代碼,執(zhí)行上下文在這個階段創(chuàng)建。執(zhí)行上下文也分為創(chuàng)建階段和執(zhí)行階段。

1.首先進入全局環(huán)境,創(chuàng)建一個全局執(zhí)行上下文,全局變量對象window,全局作用域Global,確定this指向,this==window。

2.在執(zhí)行階段,會完成變量賦值,函數(shù)引用,以及執(zhí)行其他代碼等。瀏覽器的斷點調(diào)試只能用于執(zhí)行階段。

3.在執(zhí)行階段,如果遇到函數(shù)引用,會進入函數(shù)環(huán)境,創(chuàng)建一個執(zhí)行上下文。而在這個執(zhí)行上下文中,也分為創(chuàng)建階段和執(zhí)行階段。接下來我們主要討論函數(shù)環(huán)境的創(chuàng)建階段和執(zhí)行階段,不考慮全局環(huán)境,因為全局環(huán)境也可以理解為在一個大的函數(shù)環(huán)境中。行為基本一致。

二、執(zhí)行上下文

每次當Js引擎轉(zhuǎn)到可執(zhí)行代碼的時候,就會進入一個執(zhí)行上下文。執(zhí)行上下文可以理解為當前代碼的執(zhí)行環(huán)境,它會形成一個作用域。JavaScript中的運行環(huán)境大概包括三種情況。

  • 全局環(huán)境:JavaScript代碼運行起來會首先進入該環(huán)境
  • 函數(shù)環(huán)境:當函數(shù)被調(diào)用執(zhí)行時,會進入當前函數(shù)中執(zhí)行代碼
  • eval

當代碼在執(zhí)行過程中,遇到以上三種情況,都會生成一個執(zhí)行上下文,放入棧中,我們稱其為函數(shù)調(diào)用棧(call stack)。棧底永遠都是全局上下文,而棧頂就是當前正在執(zhí)行的上下文。處于棧頂?shù)纳舷挛膱?zhí)行完畢之后,就會自動出棧。

注意:函數(shù)中,遇到return能直接終止可執(zhí)行代碼的執(zhí)行,因此會直接將當前上下文彈出棧。

執(zhí)行上下文特點:

? 單線程
  ? 同步執(zhí)行,只有棧頂?shù)纳舷挛奶幱趫?zhí)行中,其他上下文需要等待
  ? 全局上下文只有唯一的一個,它在瀏覽器關閉時出棧
  ? 函數(shù)的執(zhí)行上下文的個數(shù)沒有限制
  ? 每次某個函數(shù)被調(diào)用,就會有個新的執(zhí)行上下文為其創(chuàng)建,即使是調(diào)用的自身函數(shù),也是如此。

當調(diào)用一個函數(shù)時,一個新的執(zhí)行上下文就會被創(chuàng)建。而一個執(zhí)行上下文的生命周期可以分為兩個階段。
  創(chuàng)建階段:在這個階段中,執(zhí)行上下文會分別創(chuàng)建變量對象,建立作用域鏈,以及確定this的指向。
  代碼執(zhí)行:階段創(chuàng)建完成之后,就會開始執(zhí)行代碼,這個時候,會完成變量賦值,函數(shù)引用,以及執(zhí)行其他代碼。


三、變量對象

在執(zhí)行上下文的創(chuàng)建階段,會進行變量對象的創(chuàng)建。

而變量對象的創(chuàng)建過程又分為:
  1.參數(shù)對象創(chuàng)建。建立arguments對象,檢查當前上下文中的參數(shù),建立該對象下的屬性與屬性值。
  2.函數(shù)聲明。檢查當前上下文的函數(shù)聲明,也就是使用function關鍵字聲明的函數(shù)。在變量對象中以函數(shù)名建立一個屬性,屬性值為指向該函數(shù)所在內(nèi)存地址的引用。如果函數(shù)名的屬性已經(jīng)存在,那么該屬性將會被新的引用所覆蓋。function聲明會比var聲明優(yōu)先級更高一點。
  3.變量聲明。檢查當前上下文中的變量聲明,每找到一個變量聲明,就在變量對象中以變量名建立一個屬性,屬性值為undefined。如果該變量名的屬性已經(jīng)存在,為了防止同名的函數(shù)被修改為undefined,則會直接跳過,原屬性值不會被修改。

在執(zhí)行上下文的執(zhí)行階段,進行變量對象的賦值,函數(shù)的引用等。

進入執(zhí)行階段之前,變量對象中的屬性都不能訪問!但是進入執(zhí)行階段之后,變量對象轉(zhuǎn)變?yōu)榱嘶顒訉ο螅锩娴膶傩远寄鼙辉L問了,然后開始進行執(zhí)行階段的操作。
  變量對象和活動對象其實都是同一個對象,只是處于執(zhí)行上下文的不同生命周期。

四、舉個例子:

function test() {
    var a = 1;
    function foo() {
        return 2;
    }
}

test();

我們直接從test()的執(zhí)行上下文開始理解。全局作用域中運行test()時,test()的執(zhí)行上下文開始創(chuàng)建。為了便于理解,我們用如下的形式來表示。

創(chuàng)建階段:

testEC = {
    // 變量對象
    VO: {},
    scopeChain: {},
    this: {}
}

// 因為本文暫時不詳細解釋作用域鏈和this,所以把變量對象專門提出來說明

// VO 為 Variable Object的縮寫,即變量對象
VO = {
    arguments: {...},  //參數(shù)對象。注:在瀏覽器的展示中,函數(shù)的參數(shù)可能并不是放在arguments對象中,這里為了方便理解,我做了這樣的處理
    foo: <foo reference>  // 表示foo的地址引用
    a: undefined
}

執(zhí)行階段:

VO ->  AO   // Active Object 即活動對象
AO = {
    arguments: {...},
    foo: <foo reference>,
    a: 1
}

五、變量提升

變量提升是將變量聲明提升到它所在作用域的最開始的部分。如:

console.log(a);  //undefined
console.log(b);  //Uncaught ReferenceError: b is not defined 
var a = 1;

代碼執(zhí)行分為創(chuàng)建和執(zhí)行兩個階段,代碼開始執(zhí)行的時候,創(chuàng)建階段已經(jīng)完成,所以變量對象已經(jīng)創(chuàng)建完成。此時a是undefined,而b是沒有的。
  所以也就解釋了變量提升的現(xiàn)象,因為在執(zhí)行的時候,所有的變量對象早已創(chuàng)建完成,只是還沒有被賦值。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容