<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                # JavaScript的運行機制 點擊關注本[公眾號](http://www.hmoore.net/book/dsh225/javascript_vue_css/edit#_118)獲取文檔最新更新,并可以領取配套于本指南的《**前端面試手冊**》以及**最標準的簡歷模板**. 了解JavaScript運行機制有助于我們避免bug,并寫出高性能的代碼,當然還有一大用處就是有助于我們通過造火箭環節的面試。 具體而言你會搞清楚以下問題: * 作用域鏈本質上是如何產生的 * this是如何被綁定的 * JavaScript代碼到底運行原理是什么 * 閉包產生的根本原因 而產生的『后果』是,你可以應對幾乎所有的JavaScript作用域、閉包、執行等層面的面試題,還有一個可能的后果,就是面對復雜度不是那么高的代碼時,你的腦子中會自己把執行過程像放動畫一樣過一遍(雖然這個動畫也不非常準確)。 [TOC] ## **JavaScript的執行環境** 在了解JavaScript運行機制之前,我們需要搞清楚幾個主要概念,這有助于我們接下來的理解。 ### JavaScript引擎(JavaScript Engine) 賦予一段代碼意義的正是JavaScript引擎,目前JavaScript引擎有許多種: * V8?—?開源,由 Google 開發,用 C ++ 編寫 * Rhino?—?由 Mozilla 基金會管理,開源,完全用 Java 開發 * SpiderMonkey?—?是第一個支持 Netscape Navigator 的 JavaScript 引擎,目前正供 Firefox 使用 * JavaScriptCore —?開源,以Nitro形式銷售,由蘋果為Safari開發 * KJS?—?KDE 的引擎,最初由 Harri Porten 為 KDE 項目中的 Konqueror 網頁瀏覽器開發 * Chakra (JScript9)?—?Internet Explorer * Chakra (JavaScript)?—?Microsoft Edge * Nashorn, 作為 OpenJDK 的一部分,由 Oracle Java 語言和工具組編寫 * JerryScript?—? 物聯網的輕量級引擎 而最為大家熟知的無疑是V8引擎,他用于Chrome瀏覽器和Node中。 ![2019-06-19-13-00-37](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/27d902eae39383d1e92d05f4be51ce9b.png) V8引擎由兩個主要部件組成: * emory Heap(內存堆)?—?內存分配地址的地方 * Call Stack(調用堆棧) — 代碼執行的地方 ### JavaScript運行時(JavaScript Runtime) 想讓JavaScript真正運作起來,單單靠JavaScript Engine是不夠的,JavaScript Engine的工作是**編譯并執行 JavaScript 代碼,完成內存分配、垃圾回收等**,但是缺乏與外部交互的能力。 比如單靠一個V8引擎是無法進行ajax請求、設置定時器、響應事件等操作的,這就需要JavaScript運行時(JavaScript Runtime)的幫助,它為 JavaScript 提供一些對象或機制,使它能夠與外界交互。 比如,雖然Chrome和node都是用了V8引擎,但是他們的運行時卻不同,比如process、fs瀏覽器都無法提供。 ### 可執行代碼 一段JavaScript代碼的運行我們可以分為兩個階段: * 編譯階段: * 分詞/詞法分析(Tokenizing/Lexing) * 解析/語法分析(Parsing) * 預編譯(解釋) * 執行階段 本文的重點在于執行階段。 JavaScript并非簡單的一行行解釋執行,而是將JavaScript代碼分為一塊塊的可執行代碼塊進行執行,那么如何劃分代碼塊? 目前有三類代碼塊: * 函數代碼塊(Function code) * 全局代碼塊(Global code) * eval代碼塊(Eval code) ## JavaScript執行 我們先看一個簡單的例子: ![2019-06-20-08-15-59](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/2a165649e1648896c43cd0b5ce9f33d9.png) 看到這個例子思考一下JavaScript應該是如何執行它的? 如果你頭腦里沒有任何細節的概念,那么接下來的內容就很適用于你了。 ### 堆 我們之前提到過JavaScript引擎兩個重要部分: * emory Heap(內存堆)?—?內存分配地址的地方 * Call Stack(調用棧) — 代碼執行的地方 而上面的代碼聲明正是被存放在『堆』中。 ![2019-06-20-00-15-33](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/65c06e0194c7f94e7af45e8fcb30e004.png) 此時雖然變量和函數都被聲明了,但是函數還沒有執行,我們現在執行`say`函數。 ![2019-06-20-08-16-47](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/cb4772803d189080a33facfeecd11baa.png) 那么接下來又會發生什么呢? ### 調用棧 調用棧(Call Stack)這個概念對于經常調試JavaScript代碼的同學應該不陌生。 ![2019-06-20-00-22-23](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/7e4050faa0d3ed66965ad08bf2fec42e.png) 我們聲明的函數與變量被儲存在『內存堆』中,而當我們要執行的時候,就必須借助于『調用棧』來解決問題。 如果熟悉數據結構的同學應該知道,棧是一個基礎的數據結構,它的特點就是先進后出。 我們仍然看這個例子,當`say`函數被調用的時候,他會被壓入棧底。 ![2019-06-20-00-29-02](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/14c76ec0f423e439cf0df59ad8548f8b.png) 那么是不是將函數壓入棧內就結束了?肯定沒有這么簡單,這里需 要在引入一個概念,執行上下文(execution context)。 ### 執行上下文(execution context) 執行上下文在代碼塊執行前創建,作為代碼塊運行的基本執行環境,那么執行上下文分為幾種? 前面我們提到過,JavaScript中有三種可執行代碼塊,當然也對應著三種執行上下文。 * 全局執行上下文 — 這是基礎上下文,任何不在函數內部的代碼都在全局上下文中。它會執行兩件事:創建一個全局的 window 對象(瀏覽器的情況下),并且設置 this 的值等于這個全局對象。一個程序中只會有一個全局執行上下文。 * 函數執行上下文 — 每當一個函數被調用時, 都會為該函數創建一個新的上下文。每個函數都有它自己的執行上下文,不過是在函數被調用時創建的。函數上下文可以有任意多個。每當一個新的執行上下文被創建。 * Eval 執行上下文 — 執行在 eval 內部的代碼也會有它屬于自己的執行上下文,除非你想搞黑魔法,不然不要輕易使用它。 肯定會有人好奇,這個執行上下文到底包含哪些東西呢,他是如何運行的呢? 執行上下文分為兩個階段: * 創建階段 * 執行階段 我們主要討論創建階段,執行階段的主要工作就是分配變量 #### 執行上下文的創建階段 執行上下文的創建階段主要解決以下三點: * 決定 this 的指向 * 創建詞法環境(LexicalEnvironment) * 創建變量環境(VariableEnvironment) > 你可能在一些過時的教材或者文章中見過變量對象(VO)這種說法,它的意思與詞法環境類似,但是那是ES3的標準,現在早已經改了,改變的原因討論如下[Why variable object was changed to lexical environment in ES5?](https://stackoverflow.com/questions/40544709/why-variable-object-was-changed-to-lexical-environment-in-es5) 偽代碼如下: ![2019-06-20-08-17-34](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/58ff3a1b54232bf835e0eda470404691.png) ##### this指向 我們應該知道this的指向是在代碼執行階段確定的,所謂的『代碼執行階段』正是『執行上下文的創建階段』。 默認情況下this指向全局對象,比如瀏覽器中的window. 此外可能存在隱式綁定的情況,比如通過對象調用函數: ![2019-06-20-08-18-09](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/17ac778f64d12da5c024b4fc310c2578.png) 這個時候this指向對象。 然后就是顯示綁定對象(call apply bind)等,最后優先級最高的就是new調用構造函數生成一個對象。 ##### 詞法環境(LexicalEnvironment) 詞法環境分為三大類: * 全局環境:全局環境的外部環境引用是 null,它擁有內建的 Object/Array/等、在環境記錄器內的原型函數(關聯全局對象,比如 window 對象)還有任何用戶定義的全局變量,并且 this的值指向全局對象。 * 模塊環境:包含模塊頂級聲明的綁定以及模塊顯式導入的綁定。 模塊環境的外部環境是全局環境。 * 函數環境:函數內部用戶定義的變量存儲在環境記錄器中,外部引用既可以是其它函數的內部詞法環境,也可以是全局詞法環境 詞法環境本身包括兩個部分: * 『環境記錄器(Environment Record)』是存儲變量和函數聲明的實際位置 * 『外部環境的引用(outer Lexical Environment)』指它可以訪問其父級詞法環境(即作用域) 對于『環境記錄器』而言,它又分為兩個主要的環境記錄器類型: * 聲明式環境記錄器(DecarativeEnvironmentRecord):范圍包含函數定義,變量聲明,try...catch等,此類型對應其范圍內包含的聲明定義的標識符集 * 對象式環境記錄器(ObjectEnvironmentRecord):由程序級別的(Program)對象、聲明、with語句等創建,與稱為其綁定對象的對象相關聯,此類型對應于其綁定對象的屬性名稱的字符串標識符名稱集 比如我們在全局聲明一個函數: ![2019-06-20-08-18-42](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/90c3f805aeba811d2b75097a5b3fba48.png) 那么他的詞法環境可以這樣表示(下圖我們省略了this綁定、變量環境等信息,便于理解): ![2019-06-20-03-49-33](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/f2fd3a92e2aa96c5005d525389834a57.png) ##### 變量環境(VariableEnvironment) 變量環境的定義在es5標準和es6標準是略有不同的,我們采用[es6的標準](http://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation) 變量環境也是一個詞法環境,但不同的是詞法環境被用來存儲函數聲明和變量(let 和 const)綁定,而變量環境只用來存儲 var 變量綁定。 ### 執行過程 在了解了這么多概念之后,我們就可以把本節開頭的例子再拓展一下: ![2019-06-20-08-19-15](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/8532b28d02cf78652a370c82a6c2d29a.png) 我們就一步步復盤一下上述代碼是如何執行的(不考慮解析、預解釋等操作,只考慮執行): 1. 變量`name`和函數聲明`say`被白存在堆中。 ![2019-06-20-05-23-40](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/e1f42e04400e14c49c32f51327f85789.png) 2. 創建全局可執行上下文: 全局上下文的偽代碼如下: ![2019-06-20-08-19-53](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/2fd22918e0c60c3dacf7fdf3c2c28c3b.png) 示意圖: ![2019-06-20-05-48-54](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/ac8d769de3c77bd724b0f98221c3f8d6.png) 3. 創建函數執行上下文 say函數的執行上下文偽代碼如下: ![2019-06-20-08-20-53](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/e3dd5ee7ef882c94d27ed55a546779d5.png) 4. 創建創建say函數體內的函數執行上下文 play函數的執行上下文偽代碼如下 ![2019-06-20-08-21-45](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/885a56c1ebb11cfbc1588d5f51fbaee9.png) 示意圖: ![2019-06-20-06-00-27](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/0f1701f3b7061942ae24a9357f28bc2e.png) 5. 開始執行 將上下文中的變量賦值,然后執行代碼,執行完畢棧頂的play函數后彈出,接著執行say函數,完畢后彈出。 ## 小結 我們通過本文了解了相關的JavaScript執行機制,現在可以回答這幾個問題了。 ### this是怎么被綁定的? 在創建可執行上下文的時候,根據代碼的執行條件,來判斷分別進行默認綁定、隱式綁定、顯示綁定等。 ### 作用域鏈是怎么形成的? 可執行上下文中的詞法環境中含有外部詞法環境的引用,我們可以通過這個引用獲取外部詞法環境的變量、聲明等,這些引用串聯起來一直指向全局的詞法環境,因此形成了作用域鏈。 ### 閉包是怎么形成的? 可執行上下文中的詞法環境中含有外部詞法環境的引用,我們可以通過這個引用獲取外部詞法環境的變量、聲明等,因此形成了閉包。 * * * 參考 1. [ecma標準](http://www.ecma-international.org/ecma-262/6.0/#sec-for-statement-runtime-semantics-labelledevaluation) 2. [JavaScript調用棧到異步](https://www.valentinog.com/blog/engines/) * * * ## 公眾號 想要實時關注筆者最新的文章和最新的文檔更新請關注公眾號**程序員面試官**,后續的文章會優先在公眾號更新. **簡歷模板**:關注公眾號回復「模板」獲取 **《前端面試手冊》**:配套于本指南的突擊手冊,關注公眾號回復「fed」獲取 ![2019-08-12-03-18-41](https://xiaomuzhu-image.oss-cn-beijing.aliyuncs.com/d846f65d5025c4b6c4619662a0669503.png)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看