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

資訊專欄INFORMATION COLUMN

JavaScript的執(zhí)行上下文與執(zhí)行環(huán)境

ZHAO_ / 2699人閱讀

摘要:也就是說,當代碼執(zhí)行的時候,會進入不同的執(zhí)行上下文,這些執(zhí)行上下文就構成了一個執(zhí)行上下文棧,。它是一個與上下文相關的特殊對象,其中存儲了在上下文中定義的變量和函數(shù)聲明。

明白的人,看標題這么寫,會發(fā)現(xiàn)是有問題的,對的,在JavaScript中執(zhí)行上下文執(zhí)行環(huán)境是同一個東西,標題這么寫肯定是有問題的。但是有些人是搞不清執(zhí)行上下文執(zhí)行環(huán)境的,所以我才這么寫,以便于他們好搜索到。下面我們統(tǒng)稱執(zhí)行上下文Execution contextEC)。

在JavaScript的運行過程中,經(jīng)常會遇到一些"奇怪"的行為,不理解為什么JavaScript會這么工作。這時候可能就需要了解一下JavaScript執(zhí)行過程中的相關內容了。

執(zhí)行上下文

在JavaScript中有三種代碼運行環(huán)境:

Global Code
    JavaScript代碼開始運行的默認環(huán)境
Function Code
    代碼進入一個JavaScript函數(shù)
Eval Code
    使用eval()執(zhí)行代碼

為了表示不同的運行環(huán)境,JavaScript中有一個執(zhí)行上下文(Execution context,EC)的概念。也就是說,當JavaScript代碼執(zhí)行的時候,會進入不同的執(zhí)行上下文,這些執(zhí)行上下文就構成了一個執(zhí)行上下文棧(Execution context stack,ECS)。

例如對如下面的JavaScript代碼:

var a = "global var";

function foo(){
    console.log(a);
}

function outerFunc(){
    var b = "var in outerFunc";
    console.log(b);
    
    function innerFunc(){
        var c = "var in innerFunc";
        console.log(c);
        foo();
    }
    
    innerFunc();
}


outerFunc()

代碼首先進入Global Execution Context,然后依次進入outerFunc,innerFunc和foo的執(zhí)行上下文,執(zhí)行上下文棧就可以表示為:


當JavaScript代碼執(zhí)行的時候,第一個進入的總是默認的Global Execution Context,所以說它總是在ECS的最底部。

對于每個Execution Context都有三個重要的屬性,變量對象(Variable object,VO),作用域鏈(Scope chain)和this。這三個屬性跟代碼運行的行為有很重要的關系,下面會一一介紹。

當然,除了這三個屬性之外,根據(jù)實現(xiàn)的需要,Execution Context還可以有一些附加屬性。

VO和AO

從上面看到,在Execution Context中,會保存變量對象(Variable object,VO),下面就看看變量對象是什么。

變量對象(Variable object)

變量對象是與執(zhí)行上下文相關的數(shù)據(jù)作用域。它是一個與上下文相關的特殊對象,其中存儲了在上下文中定義的變量和函數(shù)聲明。也就是說,一般VO中會包含以下信息:

變量 (var, Variable Declaration);
函數(shù)聲明 (Function Declaration, FD);
函數(shù)的形參

當JavaScript代碼運行中,如果試圖尋找一個變量的時候,就會首先查找VO。對于前面例子中的代碼,Global Execution Context中的VO就可以表示如下:

注意,假如上面的例子代碼中有下面兩個語句,Global VO仍將不變。

(function bar(){}) // function expression, FE
baz = "property of global object"

也就是說,對于VO,是有下面兩種特殊情況的:

1.函數(shù)表達式(與函數(shù)聲明相對)不包含在VO之中
2.沒有使用var聲明的變量(這種變量是,"全局"的聲明方式,只是給Global添加了一個屬性,并不在VO中)
活動對象(Activation object)

只有全局上下文的變量對象允許通過VO的屬性名稱間接訪問;在函數(shù)執(zhí)行上下文中,VO是不能直接訪問的,此時由激活對象(Activation Object,縮寫為AO)扮演VO的角色。激活對象 是在進入函數(shù)上下文時刻被創(chuàng)建的,它通過函數(shù)的arguments屬性初始化。

Arguments Objects 是函數(shù)上下文里的激活對象AO中的內部對象,它包括下列屬性:

callee:指向當前函數(shù)的引用
length: 真正傳遞的參數(shù)的個數(shù)
properties-indexes:就是函數(shù)的參數(shù)值(按參數(shù)列表從左到右排列)

對于VO和AO的關系可以理解為,VO在不同的Execution Context中會有不同的表現(xiàn):當在Global Execution Context中,可以直接使用VO;但是,在函數(shù)Execution Context中,AO就會被創(chuàng)建。

當上面的例子開始執(zhí)行outerFunc的時候,就會有一個outerFunc的AO被創(chuàng)建:

通過上面的介紹,我們現(xiàn)在了解了VO和AO是什么,以及他們之間的關系了。下面就需要看看JavaScript解釋器是怎么執(zhí)行一段代碼,以及設置VO和AO了。

細看Execution Context

當一段JavaScript代碼執(zhí)行的時候,JavaScript解釋器會創(chuàng)建Execution Context,其實這里會有兩個階段:

1.創(chuàng)建階段(當函數(shù)被調用,但是開始執(zhí)行函數(shù)內部代碼之前)
    創(chuàng)建Scope chain
    創(chuàng)建VO/AO(variables, functions and arguments)
    設置this的值
2.激活/代碼執(zhí)行階段
    設置變量的值、函數(shù)的引用,然后解釋/執(zhí)行代碼

這里想要詳細介紹一下"創(chuàng)建VO/AO"中的一些細節(jié),因為這些內容將直接影響代碼運行的行為。

對于"創(chuàng)建VO/AO"這一步,JavaScript解釋器主要做了下面的事情:

1.根據(jù)函數(shù)的參數(shù),創(chuàng)建并初始化arguments object
2.掃描函數(shù)內部代碼,查找函數(shù)聲明(Function declaration)
    對于所有找到的函數(shù)聲明,將函數(shù)名和函數(shù)引用存入VO/AO中
    如果VO/AO中已經(jīng)有同名的函數(shù),那么就進行覆蓋
3.掃描函數(shù)內部代碼,查找變量聲明(Variable declaration)
    對于所有找到的變量聲明,將變量名存入VO/AO中,并初始化為"undefined"
    如果變量名稱跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會干擾已經(jīng)存在的這類屬性

看下面的例子:

function foo(i) {
    var a = "hello";
    var b = function privateB() {

    };
    function c() {

    }
}

foo(22);

對于上面的代碼,在"創(chuàng)建階段",可以得到下面的Execution Context object:

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

在"激活/代碼執(zhí)行階段",Execution Context object就被更新為:

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

例子分析

前面介紹了Execution Context,VO/AO等這么多的理論知識,當然是為了方便我們去分析代碼中的一些行為。這里,就通過幾個簡單的例子,結合上面的概念來分析結果。

Example 1
首先看第一個例子:

(function(){
    console.log(bar);
    console.log(baz);
    
    var bar = 20;
    
    function baz(){
        console.log("baz");
    }
    
})()

在Chrome中運行代碼運行后將輸出:

代碼解釋:匿名函數(shù)會首先進入"創(chuàng)建結果",JavaScript解釋器會創(chuàng)建一個"Function Execution Context",然后創(chuàng)建Scope chain,VO/AO和this。根據(jù)前面的介紹,解釋器會掃描函數(shù)和變量聲明,如下的AO會被創(chuàng)建:

所以,對于bar,我們會得到"undefined"這個輸出,表現(xiàn)的行為就是,我們在聲明一個變量之前就訪問了這個變量。這個就是JavaScript中"Hoisting"。

Example 2
接著上面的例子,進行一些修改:

(function(){
    console.log(bar);
    console.log(baz);
    
    bar = 20;
    console.log(window.bar);
    console.log(bar);
    
    function baz(){
        console.log("baz");
    }
    
})()

運行這段代碼會得到"bar is not defined(…)"錯誤。當代碼執(zhí)行到"console.log(bar);"的時候,會去AO中查找"bar"。但是,根據(jù)前面的解釋,函數(shù)中的"bar"并沒有通過var關鍵字聲明,所有不會被存放在AO中,也就有了這個錯誤。

注釋掉"console.log(bar);",再次運行代碼,可以得到下面結果。"bar"在"激活/代碼執(zhí)行階段"被創(chuàng)建。

Example 3
現(xiàn)在來看最后一個例子:

(function(){
    console.log(foo);
    console.log(bar);
    console.log(baz);
    
    var foo = function(){};
    
    function bar(){
        console.log("bar");
    }
    
    var bar = 20;
    console.log(bar);
    
    function baz(){
        console.log("baz");
    }
    
})()

代碼的運行結果為:

代碼中,最"奇怪"的地方應該就是"bar"的輸出了,第一次是一個函數(shù),第二次是"20"。

其實也很好解釋,回到前面對"創(chuàng)建VO/AO"的介紹,在創(chuàng)建VO/AO過程中,解釋器會先掃描函數(shù)聲明,然后"foo: "就被保存在了AO中;但解釋器掃描變量聲明的時候,雖然發(fā)現(xiàn)"var bar = 20;",但是因為"foo"在AO中已經(jīng)存在,所以就沒有任何操作了。

但是,當代碼執(zhí)行到第二句"console.log(bar);"的時候,"激活/代碼執(zhí)行階段"已經(jīng)把AO中的"bar"重新設置了。

作者:田小計劃
出處:http://www.cnblogs.com/wilber...
本文版權歸作者和博客園共有,歡迎轉載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

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

轉載請注明本文地址:http://systransis.cn/yun/94854.html

相關文章

  • javascript高級程序設計》筆記:內存執(zhí)行環(huán)境

    摘要:因此,所有在方法中定義的變量都是放在棧內存中的當我們在程序中創(chuàng)建一個對象時,這個對象將被保存到運行時數(shù)據(jù)區(qū)中,以便反復利用因為對象的創(chuàng)建成本通常較大,這個運行時數(shù)據(jù)區(qū)就是堆內存。 上一篇:《javascript高級程序設計》筆記:繼承近幾篇博客都會圍繞著圖中的知識點展開 showImg(https://segmentfault.com/img/bVY0C4?w=1330&h=618);...

    fuyi501 評論0 收藏0
  • 由 ECMA 規(guī)范解讀 Javascript執(zhí)行下文概念

    摘要:不包括作為其嵌套函數(shù)的被解析的源代碼。作用域鏈當代碼在一個環(huán)境中執(zhí)行時,會創(chuàng)建變量對象的一個作用域鏈。棧結構最頂層的執(zhí)行環(huán)境稱為當前運行的執(zhí)行環(huán)境,最底層是全局執(zhí)行環(huán)境。無限制函數(shù)上下文。或者拋出異常退出一個執(zhí)行環(huán)境。 前言 其實規(guī)范這東西不是給人看的,它更多的是給語言實現(xiàn)者提供參考。但是當碰到問題找不到答案時,規(guī)范往往能提供想要的答案 。偶爾讀一下能夠帶來很大的啟發(fā)和思考,如果只讀一...

    daryl 評論0 收藏0
  • 深入理解JavaScript (1) —— 執(zhí)行下文執(zhí)行下文

    摘要:執(zhí)行上下文棧通過上文我們知道預處理全局代碼時,會產(chǎn)生一個執(zhí)行上下文環(huán)境。實現(xiàn)這一壓棧出棧過程的機制就是執(zhí)行上下文棧。 JavaScript的解析(預處理)與執(zhí)行 詳見:http://www.cnblogs.com/foodoi... 執(zhí)行上下文 JavaScript在執(zhí)行一個代碼段之前,即解析(預處理)階段,會先進行一些準備工作,例如掃描JS中var定義的變量、函數(shù)名等,進而生成執(zhí)行上...

    lidashuang 評論0 收藏0
  • 前端進擊巨人(一):執(zhí)行下文執(zhí)行棧,變量對象

    摘要:在中,通過棧的存取方式來管理執(zhí)行上下文,我們可稱其為執(zhí)行棧,或函數(shù)調用棧。而處于棧頂?shù)氖钱斍罢趫?zhí)行函數(shù)的執(zhí)行上下文,當函數(shù)調用完成后,它就會從棧頂被推出理想的情況下,閉包會阻止該操作,閉包后續(xù)文章深入詳解。 寫在開篇 已經(jīng)不敢自稱前端小白,曾經(jīng)吹過的牛逼總要一點點去實現(xiàn)。 正如前領導說的,自己喝酒吹過的牛皮,跪著都得含著淚去實現(xiàn)。 那么沒有年終完美總結,來個新年莽撞開始可好。 進擊巨...

    _Suqin 評論0 收藏0
  • JavaScript執(zhí)行下文和變量對象

    摘要:以上簡單總結了下對執(zhí)行上下文和變量對象的理解,主要在于記錄總結一下學習成果,目前文章的水平實在不敢談分享。 執(zhí)行上下文(Execution Context) 文章同步到github javaScript中的執(zhí)行上下文和變量對象 JavaScript代碼執(zhí)行的過程,包括編譯和執(zhí)行兩個階段,編譯就是通過詞法分析,構建抽象抽象語法樹,并編譯成機器識別的指令,在JavaScript代碼編譯階段...

    why_rookie 評論0 收藏0

發(fā)表評論

0條評論

ZHAO_

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<