成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

深入理解JavaScript執(zhí)行上下文、函數(shù)堆棧、提升的概念

hatlonely / 2277人閱讀

摘要:原文鏈接變量對象是說的執(zhí)行上下文中都有個(gè)對象用來存放執(zhí)行上下文中可被訪問但是不能被的函數(shù)標(biāo)示符形參變量聲明等。對于函數(shù)的形參沒有什么可說的,主要看一下函數(shù)的聲明以及變量的聲明兩個(gè)部分。

首先明確幾個(gè)概念:

EC:函數(shù)執(zhí)行環(huán)境(或執(zhí)行上下文),Execution Context

ECS:執(zhí)行環(huán)境棧,Execution Context Stack

VO:變量對象,Variable Object

AO:活動(dòng)對象,Active Object

scope chain:作用域鏈

想當(dāng)初自己看到這幾個(gè)概念的時(shí)候是一(m)臉(d)懵(z)逼(z),但是不得不說這幾個(gè)概念對以后深入學(xué)習(xí)JS有很大的幫助。來不及解釋了,趕緊上車~

EC(執(zhí)行上下文)

每次當(dāng)控制器轉(zhuǎn)到ECMAScript可執(zhí)行代碼的時(shí)候,就會進(jìn)入到一個(gè)執(zhí)行上下文。

那什么是可執(zhí)行代碼呢?

可執(zhí)行代碼的類型
全局代碼(Global code

這種類型的代碼是在"程序"級處理的:例如加載外部的js文件或者本地標(biāo)簽內(nèi)的代碼。全局代碼不包括任何function體內(nèi)的代碼。 這個(gè)是默認(rèn)的代碼運(yùn)行環(huán)境,一旦代碼被載入,引擎最先進(jìn)入的就是這個(gè)環(huán)境。

函數(shù)代碼(Function code

任何一個(gè)函數(shù)體內(nèi)的代碼,但是需要注意的是,具體的函數(shù)體內(nèi)的代碼是不包括內(nèi)部函數(shù)的代碼。

Eval代碼(Eval code

eval內(nèi)部的代碼

這里僅僅引入EC這個(gè)概念,后面還有關(guān)于EC建立細(xì)節(jié)的介紹。

ECS(執(zhí)行環(huán)境棧)

我們用MDN上的一個(gè)例子來引入函數(shù)執(zhí)行棧的概念

function foo(i) {
  if (i < 0) return;
  console.log("begin:" + i);
  foo(i - 1);
  console.log("end:" + i);
}
foo(2);

// 輸出:

// begin:2
// begin:1
// begin:0
// end:0
// end:1
// end:2

這里先不關(guān)心執(zhí)行結(jié)果。磨刀不誤砍柴功,先了解一下函數(shù)執(zhí)行上下文堆棧的概念。相信弄明白了下面的概念,一切也就水落石出了

我們都知道,瀏覽器中的JS解釋器被實(shí)現(xiàn)為單線程,這也就意味著同一時(shí)間只能發(fā)生一件事情,其他的行為或事件將會被放在叫做執(zhí)行棧里面排隊(duì)。下面的圖是單線程棧的抽象視圖:

當(dāng)瀏覽器首次載入你的腳本,它將默認(rèn)進(jìn)入全局執(zhí)行上下文。如果,你在你的全局代碼中調(diào)用一個(gè)函數(shù),你程序的時(shí)序?qū)⑦M(jìn)入被調(diào)用的函數(shù),并創(chuàng)建一個(gè)新的執(zhí)行上下文,并將新創(chuàng)建的上下文壓入執(zhí)行棧的頂部。

如果你調(diào)用當(dāng)前函數(shù)內(nèi)部的其他函數(shù),相同的事情會在此上演。代碼的執(zhí)行流程進(jìn)入內(nèi)部函數(shù),創(chuàng)建一個(gè)新的執(zhí)行上下文并把它壓入執(zhí)行棧的頂部。瀏覽器總會執(zhí)行位于棧頂?shù)膱?zhí)行上下文,一旦當(dāng)前上下文函數(shù)執(zhí)行結(jié)束,它將被從棧頂彈出,并將上下文控制權(quán)交給當(dāng)前的棧。這樣,堆棧中的上下文就會被依次執(zhí)行并且彈出堆棧,直到回到全局的上下文。

看到這里,想必大家都已經(jīng)深諳上述例子輸出結(jié)果的原因了,這里我大概繪了一個(gè)流程圖來幫助理解。

VO(變量對象)/AO(活動(dòng)對象)

這里為什么要用一個(gè)/呢?按照字面理解,AO其實(shí)就是被激活的VO,兩個(gè)其實(shí)是一個(gè)東西。下面引用知乎上的一段話,幫助理解一下。原文鏈接

變量對象(Variable object)是說JS的執(zhí)行上下文中都有個(gè)對象用來存放執(zhí)行上下文中可被訪問但是不能被delete函數(shù)標(biāo)示符、形參、變量聲明等。它們會被掛在這個(gè)對象上,對象的屬性對應(yīng)它們的名字對象屬性的值對應(yīng)它們的值但這個(gè)對象是規(guī)范上或者說是引擎實(shí)現(xiàn)上的不可在JS環(huán)境中訪問到活動(dòng)對象

激活對象(Activation object)有了變量對象存每個(gè)上下文中的東西,但是它什么時(shí)候能被訪問到呢?就是每進(jìn)入一個(gè)執(zhí)行上下文時(shí),這個(gè)執(zhí)行上下文兒中的變量對象就被激活,也就是該上下文中的函數(shù)標(biāo)示符、形參、變量聲明等就可以被訪問到了

EC建立的細(xì)節(jié)
1、創(chuàng)建階段【當(dāng)函數(shù)被調(diào)用,但未執(zhí)行任何其內(nèi)部代碼之前】

創(chuàng)建作用域鏈(Scope Chain)

創(chuàng)建變量,函數(shù)和參數(shù)。

求”this“的值

2、執(zhí)行階段

初始化變量的值和函數(shù)的引用,解釋/執(zhí)行代碼。

我們可以將每個(gè)執(zhí)行上下文抽象為一個(gè)對象,這個(gè)對象具有三個(gè)屬性

ECObj: {
    scopeChain: { /* 變量對象(variableObject)+ 所有父級執(zhí)行上下文的變量對象*/ }, 
    variableObject: { /*函數(shù) arguments/參數(shù),內(nèi)部變量和函數(shù)聲明 */ }, 
    this: {} 
}
解釋器執(zhí)行代碼的偽邏輯

1、查找調(diào)用函數(shù)的代碼。

2、執(zhí)行代碼之前,先進(jìn)入創(chuàng)建上下文階段:

初始化作用域鏈

創(chuàng)建變量對象:

創(chuàng)建arguments對象,檢查上下文,初始化參數(shù)名稱和值并創(chuàng)建引用的復(fù)制。

掃描上下文的函數(shù)聲明(而非函數(shù)表達(dá)式)

為發(fā)現(xiàn)的每一個(gè)函數(shù),在變量對象上創(chuàng)建一個(gè)屬性——確切的說是函數(shù)的名字——其有一個(gè)指向函數(shù)在內(nèi)存中的引用。

如果函數(shù)的名字已經(jīng)存在,引用指針將被重寫。

掃描上下文的變量聲明

為發(fā)現(xiàn)的每個(gè)變量聲明,在變量對象上創(chuàng)建一個(gè)屬性——就是變量的名字,并且將變量的值初始化為undefined

如果變量的名字已經(jīng)在變量對象里存在,將不會進(jìn)行任何操作并繼續(xù)掃描。

求出上下文內(nèi)部“this”的值。

3、激活/代碼執(zhí)行階段:

在當(dāng)前上下文上運(yùn)行/解釋函數(shù)代碼,并隨著代碼一行行執(zhí)行指派變量的值。

VO --- 對應(yīng)上述第二個(gè)階段
        function foo(i){
            var a = "hello"
            var b = function(){}
            function c(){}
        }
        foo(22)
        

當(dāng)我們調(diào)用foo(22)時(shí),整個(gè)創(chuàng)建階段是下面這樣的

    ECObj = {
        scopChain: {...},
         variableObject: {
            arguments: {
                0: 22,
                length: 1
            },
            i: 22,
            c: pointer to function c()
            a: undefined,
            b: undefined
        },
        this: { ... }
    }

正如我們看到的,在上下文創(chuàng)建階段,VO的初始化過程如下(該過程是有先后順序的:函數(shù)的形參==>>函數(shù)聲明==>>變量聲明):

函數(shù)的形參(當(dāng)進(jìn)入函數(shù)執(zhí)行上下文時(shí)) —— 變量對象的一個(gè)屬性,其屬性名就是形參的名字,其值就是實(shí)參的值;對于沒有傳遞的參數(shù),其值為undefined

函數(shù)聲明(FunctionDeclaration, FD) —— 變量對象的一個(gè)屬性,其屬性名和值都是函數(shù)對象創(chuàng)建出來的;如果變量對象已經(jīng)包含了相同名字的屬性,則替換它的值

變量聲明(var,VariableDeclaration) —— 變量對象的一個(gè)屬性,其屬性名即為變量名,其值為undefined;如果變量名和已經(jīng)聲明的函數(shù)名或者函數(shù)的參數(shù)名相同,則不會影響已經(jīng)存在的屬性。

對于函數(shù)的形參沒有什么可說的,主要看一下函數(shù)的聲明以及變量的聲明兩個(gè)部分。

1、如何理解函數(shù)聲明過程中如果變量對象已經(jīng)包含了相同名字的屬性,則替換它的值這句話?

看如下這段代碼:

    
function foo1(a){
    console.log(a)
    function a(){} 
}
foo1(20)//"function a(){}"

根據(jù)上面的介紹,我們知道VO創(chuàng)建過程中,函數(shù)形參的優(yōu)先級是高于函數(shù)的聲明的,結(jié)果是函數(shù)體內(nèi)部聲明的function a(){}覆蓋了函數(shù)形參a的聲明,因此最后輸出a是一個(gè)function

2、如何理解變量聲明過程中如果變量名和已經(jīng)聲明的函數(shù)名或者函數(shù)的參數(shù)名相同,則不會影響已經(jīng)存在的屬性這句話?

//情景一:與參數(shù)名相同
function foo2(a){
    console.log(a)
    var a = 10
}
foo2(20) //"20"

//情景二:與函數(shù)名相同

function foo2(){
    console.log(a)
    var a = 10
    function a(){}
}
foo2() //"function a(){}"

下面是幾個(gè)比較有趣的例子,當(dāng)做加餐小菜,大家細(xì)細(xì)品味。這里給出一句話當(dāng)做參考:

函數(shù)的聲明比變量優(yōu)先級要高,并且定義過程不會被變量覆蓋,除非是賦值

function foo3(a){
    var a = 10
    function a(){}
    console.log(a)
}
foo3(20) //"10"

function foo3(a){
    var a 
    function a(){}
    console.log(a)
}
foo3(20) //"function a(){}"
AO --- 對應(yīng)第三個(gè)階段

正如我們看到的,創(chuàng)建的過程僅負(fù)責(zé)處理定義屬性的名字,而并不為他們指派具體的值,當(dāng)然還有對形參/實(shí)參的處理。一旦創(chuàng)建階段完成,執(zhí)行流進(jìn)入函數(shù)并且激活/代碼執(zhí)行階段,看下函數(shù)執(zhí)行完成后的樣子:

ECObj = {
    scopeChain: { ... },
    variableObject: {
        arguments: {
            0: 22,
            length: 1
        },
        i: 22,
        c: pointer to function c()
        a: "hello",
        b: pointer to function privateB()
    },
    this: { ... }
}
提升(Hoisting)

對于下面的代碼,相信很多人都能一眼看出輸出結(jié)果,但是卻很少有人能給出為什么會產(chǎn)生這種輸出結(jié)果的解釋。

(function() {
    console.log(typeof foo); // 函數(shù)指針
    console.log(typeof bar); // undefined

    var foo = "hello",
        bar = function() {
            return "world";
        };
        
    function foo() {
        return "hello";
    }
}());

1、為什么我們能在foo聲明之前訪問它?
回想在VO的創(chuàng)建階段,我們知道函數(shù)在該階段就已經(jīng)被創(chuàng)建在變量對象中。所以在函數(shù)開始執(zhí)行之前,foo已經(jīng)被定義了。

2、Foo被聲明了兩次,為什么foo顯示為函數(shù)而不是undefined或字符串?
我們知道,在創(chuàng)建階段,函數(shù)聲明是優(yōu)先于變量被創(chuàng)建的。而且在變量的創(chuàng)建過程中,如果發(fā)現(xiàn)VO中已經(jīng)存在相同名稱的屬性,則不會影響已經(jīng)存在的屬性。
因此,對foo()函數(shù)的引用首先被創(chuàng)建在活動(dòng)對象里,并且當(dāng)我們解釋到var foo時(shí),我們看見foo屬性名已經(jīng)存在,所以代碼什么都不做并繼續(xù)執(zhí)行。

3、為什么bar的值是undefined?
bar采用的是函數(shù)表達(dá)式的方式來定義的,所以bar實(shí)際上是一個(gè)變量,但變量的值是函數(shù),并且我們知道變量在創(chuàng)建階段被創(chuàng)建但他們被初始化為undefined,這也是為什么函數(shù)表達(dá)式不會被提升的原因。

總結(jié):

1、EC分為兩個(gè)階段,創(chuàng)建執(zhí)行上下文和執(zhí)行代碼。
2、每個(gè)EC可以抽象為一個(gè)對象,這個(gè)對象具有三個(gè)屬性,分別為:作用域鏈Scope,VO|AOAOVO只能有一個(gè))以及this。
3、函數(shù)EC中的AO在進(jìn)入函數(shù)EC時(shí),確定了Arguments對象的屬性;在執(zhí)行函數(shù)EC時(shí),其它變量屬性具體化。
4、EC創(chuàng)建的過程是由先后順序的:參數(shù)聲明 > 函數(shù)聲明 > 變量聲明

參考

javascript 執(zhí)行環(huán)境,變量對象,作用域鏈
What is the Execution Context & Stack in JavaScript?
函數(shù)MDN

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/82450.html

相關(guān)文章

  • 【進(jìn)階1-1期】理解JavaScript執(zhí)行下文執(zhí)行

    摘要:首次運(yùn)行代碼時(shí),會創(chuàng)建一個(gè)全局執(zhí)行上下文并到當(dāng)前的執(zhí)行棧中。執(zhí)行上下文的創(chuàng)建執(zhí)行上下文分兩個(gè)階段創(chuàng)建創(chuàng)建階段執(zhí)行階段創(chuàng)建階段確定的值,也被稱為。 (關(guān)注福利,關(guān)注本公眾號回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開始前端進(jìn)階的第一期,本周的主題是調(diào)用堆棧,,今天是第一天 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了解本進(jìn)...

    import. 評論0 收藏0
  • 【譯】JavaScript執(zhí)行下文堆棧是什么?

    摘要:每次調(diào)用函數(shù)時(shí),都會創(chuàng)建一個(gè)新的執(zhí)行上下文。理解執(zhí)行上下文和堆??梢宰屇私獯a為什么要計(jì)算您最初沒有預(yù)料到的不同值的原因。 首發(fā):https://www.love85g.com/?p=1723 在這篇文章中,我將深入研究JavaScript最基本的部分之一,即執(zhí)行上下文。在這篇文章的最后,您應(yīng)該更清楚地了解解釋器要做什么,為什么在聲明一些函數(shù)/變量之前可以使用它們,以及它們的值是如何...

    miguel.jiang 評論0 收藏0
  • # JavaScript執(zhí)行下文和隊(duì)列(棧)關(guān)系?

    摘要:為什么會這樣這段代碼究竟是如何運(yùn)行的執(zhí)行上下文堆棧瀏覽器中的解釋器單線程運(yùn)行。瀏覽器始終執(zhí)行位于堆棧頂部的,并且一旦函數(shù)完成執(zhí)行當(dāng)前操作,它將從堆棧頂部彈出,將控制權(quán)返回到當(dāng)前堆棧中的下方上下文。確定在上下文中的值。 原文:What is the Execution Context & Stack in JavaScript? git地址:JavaScript中的執(zhí)行上下文和隊(duì)列(...

    DangoSky 評論0 收藏0
  • [譯]了解Javascript執(zhí)行下文執(zhí)行堆棧

    摘要:理解執(zhí)行上下文和執(zhí)行堆棧對于理解的其它概念如提升,范圍和閉包至關(guān)重要。正確地理解執(zhí)行上下文和執(zhí)行堆棧將幫助你更好地使用開發(fā)應(yīng)用。引擎執(zhí)行位于執(zhí)行堆棧頂部的方法。當(dāng)調(diào)用時(shí),為該函數(shù)創(chuàng)建一個(gè)新的執(zhí)行上下文,并且把它推入到當(dāng)前執(zhí)行堆棧。 By Sukhjinder Arora | Aug 28, 2018 原文 如果你是或者你想要成為一名js開發(fā)者,那么你必須了解js程序內(nèi)部的運(yùn)作。理解執(zhí)行...

    qujian 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<