<font id="zqva1"></font>
<rt id="zqva1"></rt>
  • <tt id="zqva1"></tt>
    <cite id="zqva1"></cite>

    <cite id="zqva1"><noscript id="zqva1"></noscript></cite>
      <rp id="zqva1"><meter id="zqva1"></meter></rp>

        <cite id="zqva1"></cite>
          <b id="zqva1"></b>
          <rp id="zqva1"></rp>
          <cite id="zqva1"></cite>

          <rt id="zqva1"></rt>

        1. <rp id="zqva1"></rp>

          從JavaScript的運行原理談解析效率優化【譯】

          時間:?2019-12-12閱讀:?167標簽:?效率
          原文地址:How JavaScript works: Optimizing for parsing efficiency
          原文作者:Alvin Wan
          譯者:Chor

          編寫高效率的 JavaScript ,其中一個關鍵就是要理解它的工作原理。編寫高效代碼的方法數不勝數,例如,你可以編寫對編譯器友好的 JavaScript 代碼,從而避免將一行簡單代碼的運行速度拖慢 7 倍。

          本文我們會專注講解可以最小化 Javascript 代碼解析時間的優化方法。我們進一步縮小范圍,只討論 V8 這一驅動 Electron, Node.js 和 Google Chrome 的 JS 引擎。為了理解這些對解析友好的優化方法,我們還得先討論 JavaScript 的解析過程,在深入理解代碼解析過程的基礎上,再對三個編寫更高速 JavaScript 的技巧進行一一概述。

          先簡單回顧一下 JavaScript 執行的三個階段。

          從源代碼到語法樹 —— 解析器從源碼中生成一棵 抽象語法樹

          從語法樹到字節碼 —— V8 的解釋器 Ignition 從語法樹中生成字節碼(在 2017 年之前 并沒有該步驟,具體可以看 這篇文章)。

          從字節碼到機器碼 —— V8 的編譯器 TurboFan 從字節碼中生成圖,用高度優化的機器碼替代部分字節碼。

          上述的第二和第三階段 涉及到了 JavaScript 的編譯。在這篇文章中,我們將重點介紹第一階段并解釋該階段對編寫高效 JavaScript 的影響。我們會按照從左到右、從上到下的順序介紹解析管道,該管道接受源代碼并生成一棵語法樹。


          抽象語法樹(AST)。它是在解析器(圖中藍色部分)中創建的。


          掃描器

          源代碼首先被分解成 chunk,每個 chunk 都可能采用不同的編碼,稍后會有一個字符流將所有 chunk 的編碼統一為 UTF-16。

          在解析之前,掃描器會將 UTF-16 字符流分解成 token。token 是一段腳本中具有語義的最小單元。有不同類型的 token,包括空白符(用于 自動插入分號)、標識符、關鍵字以及代理對(僅當代理對無法被識別為其它東西時才會結合成標識符)。這些 token 之后被送往預解析器中,接著再送往解析器。


          預解析器

          解析器的工作量是最少的,只要足夠跳過傳入的源代碼并進行懶解析(而不是全解析)即可。預解析器確保輸入的源代碼包含有效語法,并生成足夠的信息來正確地編譯外部函數。這個準備好的函數稍后將按需編譯。

          解析

          解析器接收到掃描器生成的 token 后,現在需要生成一個供編譯器使用的中間表示。

          首先我們來討論解析樹。解析樹,或者說 具體語法樹(CST)將源語法表示為一棵樹。每個葉子節點都是一個 token,而每個中間節點則表示一個語法規則。在英語里,語法規指的是名詞、主語等,而在編程里,語法規則指的是一個表達式。不過,解析樹的大小隨著程序大小會增長得很快。

          相反,抽象語法樹 要更加簡潔。每個中間節點表示一個結構,比如一個減法運算(-),并且這棵樹并沒有展示源代碼的所有細節。例如,由括號定義的分組是蘊含在樹的結構中的。另外,標點符號、分隔符以及空白符都被省略了。你可以在 這里 了解更多 AST 和 CST 的區別。

          接下來我們將重點放在 AST 上。以下面用 JavaScript 編寫的斐波那契程序為例:

          function fib(n) { 
            if (n <= 1) return n; 
            return fib(n-1) + fib(n-2); 
            }

          下面的 JSON 文件就是對應的抽象語法了。這是用 AST Explorer 生成的。(如果你不熟悉這個,可以點擊這里來詳細了解 如何閱讀 JSON 格式的 AST)。

          {
            "type": "Program",
            "start": 0,
            "end": 73,
            "body": [
              {
                "type": "FunctionDeclaration",
                "start": 0,
                "end": 73,
                "id": {
                  "type": "Identifier",
                  "start": 9,
                  "end": 12,
                  "name": "fib"
                },
                "expression": false,
                "generator": false,
                "async": false,
                "params": [
                  {
                    "type": "Identifier",
                    "start": 13,
                    "end": 14,
                    "name": "n"
                  }
                ],
                "body": {
                  "type": "BlockStatement",
                  "start": 16,
                  "end": 73,
                  "body": [
                    {
                      "type": "IfStatement",
                      "start": 20,
                      "end": 41,
                      "test": {
                        "type": "BinaryExpression",
                        "start": 24,
                        "end": 30,
                        "left": {
                          "type": "Identifier",
                          "start": 24,
                          "end": 25,
                          "name": "n"
                        },
                        "operator": "<=",
                        "right": {
                          "type": "Literal",
                          "start": 29,
                          "end": 30,
                          "value": 1,
                          "raw": "1"
                        }
                      },
                      "consequent": {
                        "type": "ReturnStatement",
                        "start": 32,
                        "end": 41,
                        "argument": {
                          "type": "Identifier",
                          "start": 39,
                          "end": 40,
                          "name": "n"
                        }
                      },
                      "alternate": null
                    },
                    {
                      "type": "ReturnStatement",
                      "start": 44,
                      "end": 71,
                      "argument": {
                        "type": "BinaryExpression",
                        "start": 51,
                        "end": 70,
                        "left": {
                          "type": "CallExpression",
                          "start": 51,
                          "end": 59,
                          "callee": {
                            "type": "Identifier",
                            "start": 51,
                            "end": 54,
                            "name": "fib"
                          },
                          "arguments": [
                            {
                              "type": "BinaryExpression",
                              "start": 55,
                              "end": 58,
                              "left": {
                                "type": "Identifier",
                                "start": 55,
                                "end": 56,
                                "name": "n"
                              },
                              "operator": "-",
                              "right": {
                                "type": "Literal",
                                "start": 57,
                                "end": 58,
                                "value": 1,
                                "raw": "1"
                              }
                            }
                          ]
                        },
                        "operator": "+",
                        "right": {
                          "type": "CallExpression",
                          "start": 62,
                          "end": 70,
                          "callee": {
                            "type": "Identifier",
                            "start": 62,
                            "end": 65,
                            "name": "fib"
                          },
                          "arguments": [
                            {
                              "type": "BinaryExpression",
                              "start": 66,
                              "end": 69,
                              "left": {
                                "type": "Identifier",
                                "start": 66,
                                "end": 67,
                                "name": "n"
                              },
                              "operator": "-",
                              "right": {
                                "type": "Literal",
                                "start": 68,
                                "end": 69,
                                "value": 2,
                                "raw": "2"
                              }
                            }
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            ],
            "sourceType": "module"
          }
          
          (來源:GitHub)

          上面代碼的要點是,每個非葉子節點都是一個運算符,而每個葉子節點都是操作數。這棵語法樹稍后將作為輸入傳給 JavaScript 接著要執行的兩個階段。


          三個技巧優化你的 JavaScript

          下面羅列的技巧清單中,我會省略那些已經廣泛使用的技巧,例如縮減代碼來最大化信息密度,從而使掃描器更具有時效性。另外,我也會跳過那些適用范圍很小的建議,例如避免使用非 ASCII 字符。

          提高解析性能的方法數不勝數,讓我們著眼于其中適用范圍最廣泛的方法吧。

          1.盡可能遵從工作線程

          主線程被阻塞會導致用戶交互的延遲,所以應該盡可能減少主線程上的工作。關鍵就是要識別并避免會導致主線程中某些任務長時間運行的解析行為。

          這種啟發式超出了解析器的優化范圍。例如,用戶控制的 JavaScript 代碼段可以使用 web workers 達到相同的效果。你可以閱讀 實時處理應用 和 在 angular 中使用 web workers 來了解更多信息。

          避免使用大量的內聯腳本

          內聯腳本是在主線程中處理的,根據之前的說法,應該盡量避免這樣做。事實上,除了異步和延遲加載之外,任何 JavaScript 的加載都會阻塞主線程。

          避免嵌套外層函數

          懶編譯也是發生在主線程上的。不過,如果處理得當的話,懶解析可以加快啟動速度。想要強制進行全解析的話,可以使用諸如 optimize.js(已經不維護)這樣的工具來決定進行全解析或者懶解析。

          分解超過 100kB 的文件

          將大文件分解成小文件以最大化并行腳本的加載速度。“2019 年 JavaScript 的性能開銷”一文比較了 Facebook 網站和 Reddit 網站的文件大小。前者通過在 300 多個請求中拆分大約 6MB 的 JavaScript ,成功將解析和編譯工作在主線程上的占比控制到 30%;相反,Reddit 的主線程上進行解析和編譯工作的達到了將近 80%。

          2. 使用 JSON 而不是對象字面量 —— 偶爾

          在 JavaScript 中,解析 JSON 比解析對象字面量來得更加高效。 parsing benchmark 已經證實了這一點。在不同的主流 JavaScript 執行引擎中分別解析一個 8MB 大小的文件,前者的解析速度最高可以提升 2 倍。

          2019 年谷歌開發者大會 也討論過 JSON 解析如此高效的兩個原因:

          JSON 是單字符串 token,而對象字面量可能包含大量的嵌套對象和 token;

          語法對上下文是敏感的。解析器逐字檢查源代碼,并不知道某個代碼塊是一個對象字面量。而左大括號不僅可以表明它是一個對象字面量,還可以表明它是一個解構對象或者箭頭函數。

          不過,值得注意的是,JSON.parse 同樣會阻塞主線程。對于超過 1MB 的文件,可以使用 FlatBuffers 提高解析效率

          3. 最大化代碼緩存

          最后,你可以通過完全規避解析來提高解析效率。對于服務端編譯來說, WebAssembly (WASM) 是個不錯的選擇。然而,它沒辦法替代 JavaScript。對于 JS,更合適的方法是最大化代碼緩存。

          值得注意的是,緩存并不是任何時候都生效的。在執行結束之前編譯的任何代碼都會被緩存 —— 這意味著處理器、監聽器等不會被緩存。為了最大化代碼緩存,你必須最大化執行結束之前編譯的代碼數量。其中一個方法就是使用立即執行函數(IIFE)啟發式:解析器會通過啟發式的方法標識出這些 IIFE 函數,它們會在稍后立即被編譯。因此,使用啟發式的方法可以確保一個函數在腳本執行結束之前被編譯。

          此外,緩存是基于單個腳本執行的。這意味著更新腳本將會使緩存失效。V8 團隊建議可以分割腳本或者合并腳本,從而實現代碼緩存。但是,這兩個建議是互相矛盾的。你可以閱讀“JavaScript 開發中的代碼緩存”來了解更多代碼緩存相關的信息。


          結論

          解析時間的優化涉及到工作線程的延遲解析以及通過最大化緩存來避免完全解析。理解了 V8 的解析機制后,我們也能推斷出上面沒有提到的其它優化方法。

          下面給出了更多了解解析機制的資源,這個機制通常來說同時適用于 V8 和 JavaScript 的解析。

          吐血推薦

          1.站長廣告聯盟: 整理了目前主流的廣告聯盟平臺,如果你有流量,可以作為參考選擇適合你的平臺點擊進入...

          2.休閑娛樂: 直播/交友    優惠券領取   網頁游戲   H5游戲

          鏈接: http://www.modern-decoration.com.cn/article/detial/7227

          如何提升javascript的效率?

          為了提供新鮮、別致的用戶體驗,很多網站都會使用 JavaScript來改善設計、驗證表單、檢查瀏覽器,以及Ajax請求,cookie操作等等,實現無刷新動態效果 。但是,要將大量內容在瀏覽器呈現,如果處理不好,網站性能將會急劇下降

          用Node.js編寫內存效率高的應用程序

          軟件應用程序在計算機的主存儲器中運行,我們稱之為隨機存取存儲器(RAM)。JavaScript,尤其是 NodeJS (服務端 JS)允許我們為終端用戶編寫從小型到大型的軟件項目

          提高開發效率的 9 個工具

          互聯網真是個神奇的地方,它不僅教你如何寫代碼,還提供了很多有用的工具,在開發的各個階段幫你節約寶貴時間。本文將列出 9 個有用的在線工具,對任何開發人員而言,它們都會是得力助手(排名不分先后)

          低效率開發人員的4種壞習慣

          我們都有不好的習慣,地球上沒有一個人是完美的。作為開發人員,不良習慣會嚴重影響您的效率,也會影響您周圍的人。Jack Canfield說:“習慣將決定您的未來”。想要成長為一名開發人員,就必須改掉不良習慣。

          推薦幾款能提升代碼效率的筆記應用

          編程容易產生挫折,即使作為一種業余愛好也可能是這樣。建立一個網頁,手機APP或桌面應用都是個很大的工程,好的記筆記技能是讓這個工程井然有序的關鍵,也是克服壓力、絕望和倦怠的好方法

          有哪些可以提升編程效率的技巧和方法?

          傳說程序員打字速度要快,很多人仍然會以這樣一個標準來片面判斷技術水平.拜托,你是一個程序員,不是一個打字員,打字快可以代表一些,但也并不代表什么.互聯網行業還在糾結打字速度的,不是外行,就是一個沒有獨立思考的人.

          提高開發效率的 Vue 技巧

          vue 提供了組件功能,組件又可以分為全局組件和非全局組件。區別是全局組件你可以直接在 .vue 文件中直接使用自定義的 html 即可。非全局組件必須在 Vue 的對象中定義 components 引入這個組件

          你是一直認為MySQL count(1) 比 count(*) 效率高么?

          MySQL count(1) 真的比 count(*) 快么? 反正同事們都是這么說的,我也姑且覺得對吧,那么沒有自己研究一下究竟?如果我告訴你他們一樣,你信么?

          提高遠程工作效率的工具

          近年來,遠程工作愈來愈流行。遠程工作能夠幫助雇主能夠降低運營成本,同時員工有機會實現工作與生活之間的平衡并避免通勤。下面我們就為大家推薦13個有用的工具

          使用這些 CSS 屬性選擇器來提高前端開發效率!

          屬性選擇器非常神奇。它們可以使你擺脫棘手的問題,幫助你避免添加類,并指出代碼中的一些問題。但是不要擔心,雖然屬性選擇器非常復雜和強大,但是它們很容易學習和使用。在本文中,我們將討論它們是如何運行的,并給出一些如何使用它們的想法。

          fly63.com版權所有,內容以共享、參考、研究為目的,不存在任何商業目的。其版權屬原作者所有,如有侵權,請與小編聯系!情況屬實本人將予以刪除!

          文章投稿關于web前端網站點搜索站長推薦網站地圖站長QQ:522607023

          小程序專欄: 土味情話心理測試腦筋急轉彎幽默笑話段子句子語錄成語大全

          国产精品高清视频免费 - 视频 - 在线观看 - 影视资讯 - 唯爱网