- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- JavaScript的運行原理是什么
這篇文章將為大家詳細講解有關(guān)JavaScript的運行原理是什么,文章內容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。
掃描器
源代碼首先被分解成 chunk,每個(gè) chunk 都可能采用不同的編碼,稍后會(huì )有一個(gè)字符流將所有 chunk 的編碼統一為 UTF-16。
在解析之前,掃描器會(huì )將 UTF-16 字符流分解成 token。token 是一段腳本中具有語(yǔ)義的最小單元。有不同類(lèi)型的 token,包括空白符(用于 自動(dòng)插入分號)、標識符、關(guān)鍵字以及代理對(僅當代理對無(wú)法被識別為其它東西時(shí)才會(huì )結合成標識符)。這些 token 之后被送往預解析器中,接著(zhù)再送往解析器。
預解析器
解析器的工作量是最少的,只要足夠跳過(guò)傳入的源代碼并進(jìn)行懶解析(而不是全解析)即可。預解析器確保輸入的源代碼包含有效語(yǔ)法,并生成足夠的信息來(lái)正確地編譯外部函數。這個(gè)準備好的函數稍后將按需編譯。
解析
解析器接收到掃描器生成的 token 后,現在需要生成一個(gè)供編譯器使用的中間表示。
首先我們來(lái)討論解析樹(shù)。解析樹(shù),或者說(shuō) 具體語(yǔ)法樹(shù)(CST)將源語(yǔ)法表示為一棵樹(shù)。每個(gè)葉子節點(diǎn)都是一個(gè) token,而每個(gè)中間節點(diǎn)則表示一個(gè)語(yǔ)法規則。在英語(yǔ)里,語(yǔ)法規指的是名詞、主語(yǔ)等,而在編程里,語(yǔ)法規則指的是一個(gè)表達式。不過(guò),解析樹(shù)的大小隨著(zhù)程序大小會(huì )增長(cháng)得很快。
相反,抽象語(yǔ)法樹(shù) 要更加簡(jiǎn)潔。每個(gè)中間節點(diǎn)表示一個(gè)結構,比如一個(gè)減法運算(-),并且這棵樹(shù)并沒(méi)有展示源代碼的所有細節。例如,由括號定義的分組是蘊含在樹(shù)的結構中的。另外,標點(diǎn)符號、分隔符以及空白符都被省略了。你可以在 這里 了解更多 AST 和 CST 的區別。
接下來(lái)我們將重點(diǎn)放在 AST 上。以下面用 JavaScript 編寫(xiě)的斐波那契程序為例:
function fib(n) { if (n <= 1) return n; return fib(n-1) + fib(n-2); }
下面的 JSON 文件就是對應的抽象語(yǔ)法
了。這是用 AST Explorer 生成的。(如果你不熟悉這個(gè),可以點(diǎn)擊這里來(lái)詳細了解 如何閱讀 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" } (來(lái)源:GitHub)
上面代碼的要點(diǎn)是,每個(gè)非葉子節點(diǎn)都是一個(gè)運算符,而每個(gè)葉子節點(diǎn)都是操作數。這棵語(yǔ)法樹(shù)稍后將作為輸入傳給 JavaScript 接著(zhù)要執行的兩個(gè)階段。
三個(gè)技巧優(yōu)化你的 JavaScript
下面羅列的技巧清單中,我會(huì )省略那些已經(jīng)廣泛使用的技巧,例如縮減代碼來(lái)最大化信息密度,從而使掃描器更具有時(shí)效性。另外,我也會(huì )跳過(guò)那些適用范圍很小的建議,例如避免使用非 ASCII 字符。
提高解析性能的方法數不勝數,讓我們著(zhù)眼于其中適用范圍最廣泛的方法吧。
1.盡可能遵從工作線(xiàn)程
主線(xiàn)程被阻塞會(huì )導致用戶(hù)交互的延遲,所以應該盡可能減少主線(xiàn)程上的工作。關(guān)鍵就是要識別并避免會(huì )導致主線(xiàn)程中某些任務(wù)長(cháng)時(shí)間運行的解析行為。
這種啟發(fā)式超出了解析器的優(yōu)化范圍。例如,用戶(hù)控制的 JavaScript 代碼段可以使用 web workers 達到相同的效果。你可以閱讀 實(shí)時(shí)處理應用 和 在 angular 中使用 web workers 來(lái)了解更多信息。
避免使用大量的內聯(lián)腳本
內聯(lián)腳本是在主線(xiàn)程中處理的,根據之前的說(shuō)法,應該盡量避免這樣做。事實(shí)上,除了異步和延遲加載之外,任何 JavaScript 的加載都會(huì )阻塞主線(xiàn)程。
避免嵌套外層函數
懶編譯也是發(fā)生在主線(xiàn)程上的。不過(guò),如果處理得當的話(huà),懶解析可以加快啟動(dòng)速度。想要強制進(jìn)行全解析的話(huà),可以使用諸如 optimize.js(已經(jīng)不維護)這樣的工具來(lái)決定進(jìn)行全解析或者懶解析。
分解超過(guò) 100kB 的文件
將大文件分解成小文件以最大化并行腳本的加載速度?!?019 年 JavaScript 的性能開(kāi)銷(xiāo)”一文比較了 Facebook 網(wǎng)站和 Reddit 網(wǎng)站的文件大小。前者通過(guò)在 300 多個(gè)請求中拆分大約 6MB 的 JavaScript ,成功將解析和編譯工作在主線(xiàn)程上的占比控制到 30%;相反,Reddit 的主線(xiàn)程上進(jìn)行解析和編譯工作的達到了將近 80%。
2. 使用 JSON 而不是對象字面量 —— 偶爾
在 JavaScript 中,解析 JSON 比解析對象字面量來(lái)得更加高效。 parsing benchmark 已經(jīng)證實(shí)了這一點(diǎn)。在不同的主流 JavaScript 執行引擎中分別解析一個(gè) 8MB 大小的文件,前者的解析速度最高可以提升 2 倍。
2019 年谷歌開(kāi)發(fā)者大會(huì ) 也討論過(guò) JSON 解析如此高效的兩個(gè)原因:
鴻蒙官方戰略合作共建——HarmonyOS技術(shù)社區
JSON 是單字符串 token,而對象字面量可能包含大量的嵌套對象和 token;
語(yǔ)法對上下文是敏感的。解析器逐字檢查源代碼,并不知道某個(gè)代碼塊是一個(gè)對象字面量。而左大括號不僅可以表明它是一個(gè)對象字面量,還可以表明它是一個(gè)解構對象或者箭頭函數。
不過(guò),值得注意的是,JSON.parse 同樣會(huì )阻塞主線(xiàn)程。對于超過(guò) 1MB 的文件,可以使用 FlatBuffers 提高解析效率。
3. 最大化代碼緩存
最后,你可以通過(guò)完全規避解析來(lái)提高解析效率。對于服務(wù)端編譯來(lái)說(shuō), WebAssembly (WASM) 是個(gè)不錯的選擇。然而,它沒(méi)辦法替代 JavaScript。對于 JS,更合適的方法是最大化代碼緩存。
值得注意的是,緩存并不是任何時(shí)候都生效的。在執行結束之前編譯的任何代碼都會(huì )被緩存 —— 這意味著(zhù)處理器、監聽(tīng)器等不會(huì )被緩存。為了最大化代碼緩存,你必須最大化執行結束之前編譯的代碼數量。其中一個(gè)方法就是使用立即執行函數(IIFE)啟發(fā)式:解析器會(huì )通過(guò)啟發(fā)式的方法標識出這些 IIFE 函數,它們會(huì )在稍后立即被編譯。因此,使用啟發(fā)式的方法可以確保一個(gè)函數在腳本執行結束之前被編譯。
此外,緩存是基于單個(gè)腳本執行的。這意味著(zhù)更新腳本將會(huì )使緩存失效。V8 團隊建議可以分割腳本或者合并腳本,從而實(shí)現代碼緩存。但是,這兩個(gè)建議是互相矛盾的。你可以閱讀“JavaScript 開(kāi)發(fā)中的代碼緩存”來(lái)了解更多代碼緩存相關(guān)的信息。
結論
解析時(shí)間的優(yōu)化涉及到工作線(xiàn)程的延遲解析以及通過(guò)最大化緩存來(lái)避免完全解析。理解了 V8 的解析機制后,我們也能推斷出上面沒(méi)有提到的其它優(yōu)化方法。
下面給出了更多了解解析機制的資源,這個(gè)機制通常來(lái)說(shuō)同時(shí)適用于 V8 和 JavaScript 的解析。
V8 文檔
V8 博客
V8-perf
額外小貼士:理解 JavaScript 的錯誤和性能是如何影響你的用戶(hù)的。
跟蹤生產(chǎn)過(guò)程中 JavaScript 的異?;蛘咤e誤是很耗時(shí)的,而且也很令人傷腦筋。如果你有興趣監控 JavaScript 的錯誤和應用性能是如何對用戶(hù)造成影響的,可以嘗試使用 LogRocket。
LogRocket 就像是為 web 應用量身訂造的 DVR(錄像機),它可以確切地記錄你的網(wǎng)站上發(fā)生的所有事情。LogRocket 可以幫助你統計并報告錯誤,以查看錯誤發(fā)生的頻率以及它們對你的用戶(hù)群的影響程度。你可以輕松地重現錯誤發(fā)生時(shí)特定的用戶(hù)會(huì )話(huà),以查看是用戶(hù)的哪些操作導致了 bug。
LogRocket 可以記錄你的 app 上的請求和響應(包含 header 和 body)以及用戶(hù)相關(guān)的上下文信息,從而窺探問(wèn)題全貌。它也可以記錄頁(yè)面的 HTML 和 CSS,即使是面對最復雜的單頁(yè)面應用,也可以重構出像素完美級別的視頻。
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站