對于Javascript執(zhí)行上下文的全面了解

在這篇文章中,將比較深入地闡述下執(zhí)行上下文 – JavaScript中最基礎(chǔ)也是最重要的一個概念。相信讀完這篇文章后,你就會明白javascript引擎內(nèi)部在執(zhí)行代碼以前到底做了些什么,為什么某些函數(shù)以及變量在沒有被聲明以前就可以被使用,以及它們的最終的值是怎樣被定義的。

云巖ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為創(chuàng)新互聯(lián)建站的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:13518219792(備注:SSL證書合作)期待與您的合作!

什么是執(zhí)行上下文

Javascript中代碼的運(yùn)行環(huán)境分為以下三種:

全局級別的代碼 –這個是默認(rèn)的代碼運(yùn)行環(huán)境,一旦代碼被載入,引擎最先進(jìn)入的就是這個環(huán)境。

函數(shù)級別的代碼 –當(dāng)執(zhí)行一個函數(shù)時,運(yùn)行函數(shù)體中的代碼。

Eval的代碼 –在Eval函數(shù)內(nèi)運(yùn)行的代碼。

在網(wǎng)上可以找到很多闡述作用域的資源,為了使該文便于大家理解,我們可以將“執(zhí)行上下文”看做當(dāng)前代碼的運(yùn)行環(huán)境或者作用域。下面我們來看一個示例,其中包括了全局以及函數(shù)級別的執(zhí)行上下文:

對于Javascript 執(zhí)行上下文的全面了解

上圖中,一共用4個執(zhí)行上下文。紫色的代表全局的上下文;綠色代表person函數(shù)內(nèi)的上下文;藍(lán)色以及橙色代表person函數(shù)內(nèi)的另外兩個函數(shù)的上下文。注意,不管什么情況下,只存在一個全局的上下文,該上下文能被任何其它的上下文所訪問到。也就是說,我們可以在person的上下文中訪問到全局上下文中的sayHello變量,當(dāng)然在函數(shù)firstName或者lastName中同樣可以訪問到該變量。

至于函數(shù)上下文的個數(shù)是沒有任何限制的,每到調(diào)用執(zhí)行一個函數(shù)時,引擎就會自動新建出一個函數(shù)上下文,換句話說,就是新建一個局部作用域,可以在該局部作用域中聲明私有變量等,在外部的上下文中是無法直接訪問到該局部作用域內(nèi)的元素的。在上述例子的,內(nèi)部的函數(shù)可以訪問到外部上下文中的聲明的變量,反之則行不通。那么,這到底是什么原因呢?引擎內(nèi)部是如何處理的呢?

執(zhí)行上下文堆棧

在瀏覽器中,javascript引擎的工作方式是單線程的。也就是說,某一時刻只有唯一的一個事件是被激活處理的,其它的事件被放入隊(duì)列中,等待被處理。下面的示例圖描述了這樣的一個堆棧:

對于Javascript 執(zhí)行上下文的全面了解

我們已經(jīng)知道,當(dāng)javascript代碼文件被瀏覽器載入后,默認(rèn)最先進(jìn)入的是一個全局的執(zhí)行上下文。當(dāng)在全局上下文中調(diào)用執(zhí)行一個函數(shù)時,程序流就進(jìn)入該被調(diào)用函數(shù)內(nèi),此時引擎就會為該函數(shù)創(chuàng)建一個新的執(zhí)行上下文,并且將其壓入到執(zhí)行上下文堆棧的頂部。瀏覽器總是執(zhí)行當(dāng)前在堆棧頂部的上下文,一旦執(zhí)行完畢,該上下文就會從堆棧頂部被彈出,然后,進(jìn)入其下的上下文執(zhí)行代碼。這樣,堆棧中的上下文就會被依次執(zhí)行并且彈出堆棧,直到回到全局的上下文。請看下面一個例子:

(function foo(i) {
   if (i === 3) {
    return;
   }
   else {
    foo(++i);
   }
  }(0));

上述foo被聲明后,通過()運(yùn)算符強(qiáng)制直接運(yùn)行了。函數(shù)代碼就是調(diào)用了其自身3次,每次是局部變量i增加1。每次foo函數(shù)被自身調(diào)用時,就會有一個新的執(zhí)行上下文被創(chuàng)建。每當(dāng)一個上下文執(zhí)行完畢,該上上下文就被彈出堆棧,回到上一個上下文,直到再次回到全局上下文。真?zhèn)€過程抽象如下圖:

對于Javascript 執(zhí)行上下文的全面了解

由此可見 ,對于執(zhí)行上下文這個抽象的概念,可以歸納為以下幾點(diǎn):

單線程

同步執(zhí)行

唯一的一個全局上下文

函數(shù)的執(zhí)行上下文的個數(shù)沒有限制

每次某個函數(shù)被調(diào)用,就會有個新的執(zhí)行上下文為其創(chuàng)建,即使是調(diào)用的自身函數(shù),也是如此。

執(zhí)行上下文的建立過程

我們現(xiàn)在已經(jīng)知道,每當(dāng)調(diào)用一個函數(shù)時,一個新的執(zhí)行上下文就會被創(chuàng)建出來。然而,在javascript引擎內(nèi)部,這個上下文的創(chuàng)建過程具體分為兩個階段:

建立階段(發(fā)生在當(dāng)調(diào)用一個函數(shù)時,但是在執(zhí)行函數(shù)體內(nèi)的具體代碼以前)

建立變量,函數(shù),arguments對象,參數(shù)

建立作用域鏈

確定this的值

代碼執(zhí)行階段:

變量賦值,函數(shù)引用,執(zhí)行其它代碼

實(shí)際上,可以把執(zhí)行上下文看做一個對象,其下包含了以上3個屬性:

 (executionContextObj = {
   variableObject: { /* 函數(shù)中的arguments對象, 參數(shù), 內(nèi)部的變量以及函數(shù)聲明 */ },
   scopeChain: { /* variableObject 以及所有父執(zhí)行上下文中的variableObject */ },
   this: {}
   }

建立階段以及代碼執(zhí)行階段的詳細(xì)分析

確切地說,執(zhí)行上下文對象(上述的executionContextObj)是在函數(shù)被調(diào)用時,但是在函數(shù)體被真正執(zhí)行以前所創(chuàng)建的。函數(shù)被調(diào)用時,就是我上述所描述的兩個階段中的第一個階段 – 建立階段。這個時刻,引擎會檢查函數(shù)中的參數(shù),聲明的變量以及內(nèi)部函數(shù),然后基于這些信息建立執(zhí)行上下文對象(executionContextObj)。在這個階段,variableObject對象,作用域鏈,以及this所指向的對象都會被確定。

上述第一個階段的具體過程如下:

找到當(dāng)前上下文中的調(diào)用函數(shù)的代碼

在執(zhí)行被調(diào)用的函數(shù)體中的代碼以前,開始創(chuàng)建執(zhí)行上下文

進(jìn)入第一個階段-建立階段:

建立variableObject對象:

建立arguments對象,檢查當(dāng)前上下文中的參數(shù),建立該對象下的屬性以及屬性值

檢查當(dāng)前上下文中的函數(shù)聲明:

每找到一個函數(shù)聲明,就在variableObject下面用函數(shù)名建立一個屬性,屬性值就是指向該函數(shù)在內(nèi)存中的地址的一個引用

如果上述函數(shù)名已經(jīng)存在于variableObject下,那么對應(yīng)的屬性值會被新的引用所覆蓋。

初始化作用域鏈

確定上下文中this的指向?qū)ο?/p>

代碼執(zhí)行階段:

執(zhí)行函數(shù)體中的代碼,一行一行地運(yùn)行代碼,給variableObject中的變量屬性賦值。

下面來看個具體的代碼示例:

 function foo(i) {
   var a = 'hello';
   var b = function privateB() {
  
   };
   function c() {
  
   }
  }
  
  foo(22);

在調(diào)用foo(22)的時候,建立階段如下:

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

由此可見,在建立階段,除了arguments,函數(shù)的聲明,以及參數(shù)被賦予了具體的屬性值,其它的變量屬性默認(rèn)的都是undefined。一旦上述建立階段結(jié)束,引擎就會進(jìn)入代碼執(zhí)行階段,這個階段完成后,上述執(zhí)行上下文對象如下:

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

我們看到,只有在代碼執(zhí)行階段,變量屬性才會被賦予具體的值。

局部變量作用域提升的緣由

在網(wǎng)上一直看到這樣的總結(jié): 在函數(shù)中聲明的變量以及函數(shù),其作用域提升到函數(shù)頂部,換句話說,就是一進(jìn)入函數(shù)體,就可以訪問到其中聲明的變量以及函數(shù)。這是對的,但是知道其中的緣由嗎?相信你通過上述的解釋應(yīng)該也有所明白了。不過在這邊再分析一下。

看下面一段代碼:

 (function() {
   console.log(typeof foo); // function pointer
   console.log(typeof bar); // undefined
  
   var foo = 'hello',
    bar = function() {
     return 'world';
    };
  
   function foo() {
    return 'hello';
   }
  
  }());​

上述代碼定義了一個匿名函數(shù),并且通過()運(yùn)算符強(qiáng)制理解執(zhí)行。那么我們知道這個時候就會有個執(zhí)行上下文被創(chuàng)建,我們看到例子中馬上可以訪問foo以及bar變量,并且通過typeof輸出foo為一個函數(shù)引用,bar為undefined。

為什么我們可以在聲明foo變量以前就可以訪問到foo呢?

因?yàn)樵谏舷挛牡慕㈦A段,先是處理arguments, 參數(shù),接著是函數(shù)的聲明,最后是變量的聲明。那么,發(fā)現(xiàn)foo函數(shù)的聲明后,就會在variableObject下面建立一個foo屬性,其值是一個指向函數(shù)的引用。當(dāng)處理變量聲明的時候,發(fā)現(xiàn)有var foo的聲明,但是variableObject已經(jīng)具有了foo屬性,所以直接跳過。當(dāng)進(jìn)入代碼執(zhí)行階段的時候,就可以通過訪問到foo屬性了,因?yàn)樗呀?jīng)就存在,并且是一個函數(shù)引用。

為什么bar是undefined呢?

因?yàn)閎ar是變量的聲明,在建立階段的時候,被賦予的默認(rèn)的值為undefined。由于它只要在代碼執(zhí)行階段才會被賦予具體的值,所以,當(dāng)調(diào)用typeof(bar)的時候輸出的值為undefined。

好了,到此為止,相信你應(yīng)該對執(zhí)行上下文有所理解了,這個執(zhí)行上下文的概念非常重要,務(wù)必好好搞懂之!

以上這篇對于Javascript 執(zhí)行上下文的全面了解就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持創(chuàng)新互聯(lián)。

分享題目:對于Javascript執(zhí)行上下文的全面了解
標(biāo)題URL:http://muchs.cn/article48/jpcsep.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供關(guān)鍵詞優(yōu)化、建站公司、品牌網(wǎng)站建設(shè)、定制開發(fā)、用戶體驗(yàn)、云服務(wù)器

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)頁設(shè)計公司