<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          (ES5版)深入理解 JavaScript 執(zhí)行上下文和執(zhí)行棧

          共 5116字,需瀏覽 11分鐘

           ·

          2020-03-21 23:32


          768e89bd6845b9d4b950cd5ce5d0b357.webp

          譯者序

          最近在研究 JavaScript 基礎(chǔ)性的東西,但是看到對(duì)于執(zhí)行上下文的解釋我發(fā)現(xiàn)有兩種,一種是執(zhí)行上下文包含:scope(作用域)、variable object(變量對(duì)象)、this value(this 值),另外一個(gè)種是包含:lexical environment(詞法環(huán)境)、variable environment(變量環(huán)境)、this value(this 值)。

          后面我查閱了不少博客以及 ES3 和 ES5 的規(guī)范才了解到,第一種是 ES3 的規(guī)范,經(jīng)典書籍《JavaScript 高級(jí)程序設(shè)計(jì)》第三版就是這樣解釋的,也是網(wǎng)上廣為流傳的一種,另一種是 ES5 的規(guī)范。

          然后我接著又去翻了 ES2018 中的,發(fā)現(xiàn)又有變化了,已經(jīng)增加了更多的內(nèi)容了,考慮到這部分內(nèi)容頗為復(fù)雜,準(zhǔn)備后面再進(jìn)行總結(jié)分享,查資料的時(shí)候看到這篇講執(zhí)行上下文(ES5 )的還不錯(cuò),所以就翻譯出來先分享給大家。

          以后看到變量對(duì)象、活動(dòng)對(duì)象知道是 ES3 里面的內(nèi)容,而如果是詞法環(huán)境、變量環(huán)境這種詞就是 ES5 以后的內(nèi)容。

          以下是正文:

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

          簡(jiǎn)而言之,執(zhí)行上下文是計(jì)算和執(zhí)行 JavaScript 代碼的環(huán)境的抽象概念。每當(dāng) Javascript 代碼在運(yùn)行的時(shí)候,它都是在執(zhí)行上下文中運(yùn)行。

          執(zhí)行上下文的類型

          JavaScript 中有三種執(zhí)行上下文類型。

          • 全局執(zhí)行上下文 — 這是默認(rèn)或者說基礎(chǔ)的上下文,任何不在函數(shù)內(nèi)部的代碼都在全局上下文中。它會(huì)執(zhí)行兩件事:創(chuàng)建一個(gè)全局的 window 對(duì)象(瀏覽器的情況下),并且設(shè)置 this 的值等于這個(gè)全局對(duì)象。一個(gè)程序中只會(huì)有一個(gè)全局執(zhí)行上下文。
          • 函數(shù)執(zhí)行上下文 — 每當(dāng)一個(gè)函數(shù)被調(diào)用時(shí), 都會(huì)為該函數(shù)創(chuàng)建一個(gè)新的上下文。每個(gè)函數(shù)都有它自己的執(zhí)行上下文,不過是在函數(shù)被調(diào)用時(shí)創(chuàng)建的。函數(shù)上下文可以有任意多個(gè)。每當(dāng)一個(gè)新的執(zhí)行上下文被創(chuàng)建,它會(huì)按定義的順序(將在后文討論)執(zhí)行一系列步驟。
          • Eval 函數(shù)執(zhí)行上下文 — 執(zhí)行在 eval 函數(shù)內(nèi)部的代碼也會(huì)有它屬于自己的執(zhí)行上下文,但由于 JavaScript 開發(fā)者并不經(jīng)常使用 eval,所以在這里我不會(huì)討論它。

          執(zhí)行棧

          執(zhí)行棧,也就是在其它編程語言中所說的“調(diào)用?!?,是一種擁有 LIFO(后進(jìn)先出)的數(shù)據(jù)結(jié)構(gòu),被用來存儲(chǔ)代碼運(yùn)行時(shí)創(chuàng)建的所有執(zhí)行上下文。

          當(dāng) JavaScript 引擎第一次遇到你的腳本時(shí),它會(huì)創(chuàng)建一個(gè)全局的執(zhí)行上下文并且壓入當(dāng)前執(zhí)行棧。每當(dāng)引擎遇到一個(gè)函數(shù)調(diào)用,它會(huì)為該函數(shù)創(chuàng)建一個(gè)新的執(zhí)行上下文并壓入棧的頂部。

          引擎會(huì)執(zhí)行處于棧頂?shù)膱?zhí)行上下文的函數(shù)。當(dāng)該函數(shù)執(zhí)行結(jié)束時(shí),執(zhí)行上下文從棧中彈出,控制流程到達(dá)當(dāng)前棧中的下一個(gè)上下文。

          讓我們通過下面的代碼示例來理解:

          let a = 'Hello World!';

          functionfirst() {
          console.log('Inside first function');
          second();
          console.log('Again inside first function');
          }

          functionsecond() {
          console.log('Inside second function');
          }

          first();
          console.log('Inside Global Execution Context');

          900ded96eab014b4d2a37c505c1d3d3d.webp上述代碼的執(zhí)行上下文棧。

          當(dāng)上述代碼在瀏覽器加載時(shí),JavaScript 引擎創(chuàng)建了一個(gè)全局執(zhí)行上下文并把它壓入當(dāng)前執(zhí)行棧。當(dāng)遇到 first() 函數(shù)調(diào)用時(shí),JavaScript 引擎為該函數(shù)創(chuàng)建一個(gè)新的執(zhí)行上下文并把它壓入當(dāng)前執(zhí)行棧的頂部。

          當(dāng)從 first() 函數(shù)內(nèi)部調(diào)用 second() 函數(shù)時(shí),JavaScript 引擎為 second() 函數(shù)創(chuàng)建了一個(gè)新的執(zhí)行上下文并把它壓入當(dāng)前執(zhí)行棧的頂部。當(dāng) second() 函數(shù)執(zhí)行完畢,它的執(zhí)行上下文會(huì)從當(dāng)前棧彈出,并且控制流程到達(dá)下一個(gè)執(zhí)行上下文,即 first() 函數(shù)的執(zhí)行上下文。

          當(dāng) first() 執(zhí)行完畢,它的執(zhí)行上下文從棧彈出,控制流程到達(dá)全局執(zhí)行上下文。一旦所有代碼執(zhí)行完畢,JavaScript 引擎從當(dāng)前棧中移除全局執(zhí)行上下文。

          怎么創(chuàng)建執(zhí)行上下文?

          到現(xiàn)在,我們已經(jīng)看過 JavaScript 怎樣管理執(zhí)行上下文了,現(xiàn)在讓我們了解 JavaScript 引擎是怎樣創(chuàng)建執(zhí)行上下文的。

          創(chuàng)建執(zhí)行上下文有兩個(gè)階段:1) 創(chuàng)建階段2) 執(zhí)行階段。

          創(chuàng)建階段

          在 JavaScript 代碼執(zhí)行前,執(zhí)行上下文將經(jīng)歷創(chuàng)建階段。在創(chuàng)建階段會(huì)發(fā)生三件事:

          1. this 值的決定,即我們所熟知的 this 綁定
          2. 創(chuàng)建詞法環(huán)境組件。
          3. 創(chuàng)建變量環(huán)境組件。

          所以執(zhí)行上下文在概念上表示如下:

          ExecutionContext = {
          ThisBinding = <this value>,
          LexicalEnvironment = { ... },
          VariableEnvironment = { ... },
          }

          this 綁定:**

          在全局執(zhí)行上下文中,this 的值指向全局對(duì)象。(在瀏覽器中,this引用 Window 對(duì)象)。

          在函數(shù)執(zhí)行上下文中,this 的值取決于該函數(shù)是如何被調(diào)用的。如果它被一個(gè)引用對(duì)象調(diào)用,那么 this 會(huì)被設(shè)置成那個(gè)對(duì)象,否則 this 的值被設(shè)置為全局對(duì)象或者 undefined(在嚴(yán)格模式下)。例如:

          let foo = {
          baz: function() {
          console.log(this);
          }
          }

          foo.baz(); // 'this' 引用 'foo', 因?yàn)?'baz' 被
          // 對(duì)象 'foo' 調(diào)用

          let bar = foo.baz;

          bar(); // 'this' 指向全局 window 對(duì)象,因?yàn)?/span>
          // 沒有指定引用對(duì)象

          詞法環(huán)境

          官方的 ES6[1] 文檔把詞法環(huán)境定義為

          詞法環(huán)境是一種規(guī)范類型,基于 ECMAScript 代碼的詞法嵌套結(jié)構(gòu)來定義標(biāo)識(shí)符和具體變量和函數(shù)的關(guān)聯(lián)。一個(gè)詞法環(huán)境由環(huán)境記錄器和一個(gè)可能的引用outer詞法環(huán)境的空值組成。

          簡(jiǎn)單來說詞法環(huán)境是一種持有標(biāo)識(shí)符—變量映射的結(jié)構(gòu)。(這里的標(biāo)識(shí)符指的是變量/函數(shù)的名字,而變量是對(duì)實(shí)際對(duì)象[包含函數(shù)類型對(duì)象]或原始數(shù)據(jù)的引用)。

          現(xiàn)在,在詞法環(huán)境的內(nèi)部有兩個(gè)組件:(1) 環(huán)境記錄器和 (2) 一個(gè)外部環(huán)境的引用。

          1. 環(huán)境記錄器是存儲(chǔ)變量和函數(shù)聲明的實(shí)際位置。
          2. 外部環(huán)境的引用意味著它可以訪問其父級(jí)詞法環(huán)境(作用域)。

          譯者注:外部環(huán)境已經(jīng)跟 ES3 規(guī)定的作用域的作用類似

          詞法環(huán)境有兩種類型:

          • 全局環(huán)境(在全局執(zhí)行上下文中)是沒有外部環(huán)境引用的詞法環(huán)境。全局環(huán)境的外部環(huán)境引用是 null。它擁有內(nèi)建的 Object/Array/等、在環(huán)境記錄器內(nèi)的原型函數(shù)(關(guān)聯(lián)全局對(duì)象,比如 window 對(duì)象)還有任何用戶定義的全局變量,并且 this的值指向全局對(duì)象。
          • 函數(shù)環(huán)境中,函數(shù)內(nèi)部用戶定義的變量存儲(chǔ)在環(huán)境記錄器中。并且引用的外部環(huán)境可能是全局環(huán)境,或者任何包含此內(nèi)部函數(shù)的外部函數(shù)。

          環(huán)境記錄器也有兩種類型(如上?。?/p>

          1. 聲明式環(huán)境記錄器存儲(chǔ)變量、函數(shù)和參數(shù)。
          2. 對(duì)象環(huán)境記錄器用來定義出現(xiàn)在全局上下文中的變量和函數(shù)的關(guān)系。

          簡(jiǎn)而言之,

          • 全局環(huán)境中,環(huán)境記錄器是對(duì)象環(huán)境記錄器。
          • 函數(shù)環(huán)境中,環(huán)境記錄器是聲明式環(huán)境記錄器。

          注意 — 對(duì)于函數(shù)環(huán)境,聲明式環(huán)境記錄器還包含了一個(gè)傳遞給函數(shù)的 arguments 對(duì)象(此對(duì)象存儲(chǔ)索引和參數(shù)的映射)和傳遞給函數(shù)的參數(shù)的 length

          抽象地講,詞法環(huán)境在偽代碼中看起來像這樣:

          GlobalExectionContext = {
          LexicalEnvironment: {
          EnvironmentRecord: {
          Type: "Object",
          // 在這里綁定標(biāo)識(shí)符
          }
          outer:
          }
          }

          FunctionExectionContext = {
          LexicalEnvironment: {
          EnvironmentRecord: {
          Type: "Declarative",
          // 在這里綁定標(biāo)識(shí)符
          }
          outer:
          }
          }

          變量環(huán)境:

          它同樣是一個(gè)詞法環(huán)境,其環(huán)境記錄器持有變量聲明語句在執(zhí)行上下文中創(chuàng)建的綁定關(guān)系。

          如上所述,變量環(huán)境也是一個(gè)詞法環(huán)境,所以它有著上面定義的詞法環(huán)境的所有屬性。

          在 ES6 中,詞法環(huán)境組件和變量環(huán)境的一個(gè)不同就是前者被用來存儲(chǔ)函數(shù)聲明和變量(letconst)綁定,而后者只用來存儲(chǔ) var 變量綁定。

          我們看點(diǎn)樣例代碼來理解上面的概念:

          let a = 20;
          const b = 30;
          var c;

          function multiply(e, f) {
          var g = 20;
          return e * f * g;
          }

          c = multiply(20, 30);

          執(zhí)行上下文看起來像這樣:

          GlobalExectionContext = {

          ThisBinding: ,

          LexicalEnvironment: {
          EnvironmentRecord: {
          Type: "Object",
          // 在這里綁定標(biāo)識(shí)符
          a: < uninitialized >,
          b: < uninitialized >,
          multiply: < func >
          }
          outer:
          },

          VariableEnvironment: {
          EnvironmentRecord: {
          Type: "Object",
          // 在這里綁定標(biāo)識(shí)符
          c: undefined,
          }
          outer:
          }
          }

          FunctionExectionContext = {
          ThisBinding: ,

          LexicalEnvironment: {
          EnvironmentRecord: {
          Type: "Declarative",
          // 在這里綁定標(biāo)識(shí)符
          Arguments: {0: 20, 1: 30, length: 2},
          },
          outer:
          },

          VariableEnvironment: {
          EnvironmentRecord: {
          Type: "Declarative",
          // 在這里綁定標(biāo)識(shí)符
          g: undefined
          },
          outer:
          }
          }

          注意 — 只有遇到調(diào)用函數(shù) multiply 時(shí),函數(shù)執(zhí)行上下文才會(huì)被創(chuàng)建。

          可能你已經(jīng)注意到 letconst 定義的變量并沒有關(guān)聯(lián)任何值,但 var 定義的變量被設(shè)成了 undefined。

          這是因?yàn)樵趧?chuàng)建階段時(shí),引擎檢查代碼找出變量和函數(shù)聲明,雖然函數(shù)聲明完全存儲(chǔ)在環(huán)境中,但是變量最初設(shè)置為 undefinedvar 情況下),或者未初始化(letconst 情況下)。

          這就是為什么你可以在聲明之前訪問 var 定義的變量(雖然是 undefined),但是在聲明之前訪問 letconst 的變量會(huì)得到一個(gè)引用錯(cuò)誤。

          這就是我們說的變量聲明提升。

          執(zhí)行階段

          這是整篇文章中最簡(jiǎn)單的部分。在此階段,完成對(duì)所有這些變量的分配,最后執(zhí)行代碼。

          注意 — 在執(zhí)行階段,如果 JavaScript 引擎不能在源碼中聲明的實(shí)際位置找到 let 變量的值,它會(huì)被賦值為 undefined。

          結(jié)論

          我們已經(jīng)討論過 JavaScript 程序內(nèi)部是如何執(zhí)行的。雖然要成為一名卓越的 JavaScript 開發(fā)者并不需要學(xué)會(huì)全部這些概念,但是如果對(duì)上面概念能有不錯(cuò)的理解將有助于你更輕松,更深入地理解其他概念,如變量聲明提升,作用域和閉包。

          原文鏈接是:https://blog.bitsrc.io/understanding-execution-context-and-execution-stack-in-javascript-1c9ea8642dd0

          參考資料

          [1]

          官方的 ES6: http://ecma-international.org/ecma-262/6.0/




          推薦閱讀




          我的公眾號(hào)能帶來什么價(jià)值?(文末有送書規(guī)則,一定要看)

          每個(gè)前端工程師都應(yīng)該了解的圖片知識(shí)(長(zhǎng)文建議收藏)

          為什么現(xiàn)在面試總是面試造火箭?

          瀏覽 67
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  少妇白洁在线观看 | 黄色在线观看国产高清 | 黄色天天干 | 水蜜桃成人视频 | 国产色综合视频 |