今天小編給大家分享一下JavaScript的運行原理分析的相關(guān)知識點,內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
創(chuàng)新互聯(lián)專業(yè)提供成都主機(jī)托管四川主機(jī)托管成都服務(wù)器托管四川服務(wù)器托管,支持按月付款!我們的承諾:貴族品質(zhì)、平民價格,機(jī)房位于中國電信/網(wǎng)通/移動機(jī)房,成都服務(wù)器托管服務(wù)有保障!
掃描器
源代碼首先被分解成 chunk,每個 chunk 都可能采用不同的編碼,稍后會有一個字符流將所有 chunk 的編碼統(tǒng)一為 UTF-16。
在解析之前,掃描器會將 UTF-16 字符流分解成 token。token 是一段腳本中具有語義的最小單元。有不同類型的 token,包括空白符(用于 自動插入分號)、標(biāo)識符、關(guān)鍵字以及代理對(僅當(dāng)代理對無法被識別為其它東西時才會結(jié)合成標(biāo)識符)。這些 token 之后被送往預(yù)解析器中,接著再送往解析器。
預(yù)解析器
解析器的工作量是最少的,只要足夠跳過傳入的源代碼并進(jìn)行懶解析(而不是全解析)即可。預(yù)解析器確保輸入的源代碼包含有效語法,并生成足夠的信息來正確地編譯外部函數(shù)。這個準(zhǔn)備好的函數(shù)稍后將按需編譯。
解析
解析器接收到掃描器生成的 token 后,現(xiàn)在需要生成一個供編譯器使用的中間表示。
首先我們來討論解析樹。解析樹,或者說 具體語法樹(CST)將源語法表示為一棵樹。每個葉子節(jié)點都是一個 token,而每個中間節(jié)點則表示一個語法規(guī)則。在英語里,語法規(guī)指的是名詞、主語等,而在編程里,語法規(guī)則指的是一個表達(dá)式。不過,解析樹的大小隨著程序大小會增長得很快。
相反,抽象語法樹 要更加簡潔。每個中間節(jié)點表示一個結(jié)構(gòu),比如一個減法運算(-),并且這棵樹并沒有展示源代碼的所有細(xì)節(jié)。例如,由括號定義的分組是蘊含在樹的結(jié)構(gòu)中的。另外,標(biāo)點符號、分隔符以及空白符都被省略了。你可以在 這里 了解更多 AST 和 CST 的區(qū)別。
接下來我們將重點放在 AST 上。以下面用 JavaScript 編寫的斐波那契程序為例:
function fib(n) { if (n <= 1) return n; return fib(n-1) + fib(n-2); }
下面的 JSON 文件就是對應(yīng)的抽象語法
了。這是用 AST Explorer 生成的。(如果你不熟悉這個,可以點擊這里來詳細(xì)了解 如何閱讀 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)
上面代碼的要點是,每個非葉子節(jié)點都是一個運算符,而每個葉子節(jié)點都是操作數(shù)。這棵語法樹稍后將作為輸入傳給 JavaScript 接著要執(zhí)行的兩個階段。
三個技巧優(yōu)化你的 JavaScript
下面羅列的技巧清單中,我會省略那些已經(jīng)廣泛使用的技巧,例如縮減代碼來最大化信息密度,從而使掃描器更具有時效性。另外,我也會跳過那些適用范圍很小的建議,例如避免使用非 ASCII 字符。
提高解析性能的方法數(shù)不勝數(shù),讓我們著眼于其中適用范圍最廣泛的方法吧。
1.盡可能遵從工作線程
主線程被阻塞會導(dǎo)致用戶交互的延遲,所以應(yīng)該盡可能減少主線程上的工作。關(guān)鍵就是要識別并避免會導(dǎo)致主線程中某些任務(wù)長時間運行的解析行為。
這種啟發(fā)式超出了解析器的優(yōu)化范圍。例如,用戶控制的 JavaScript 代碼段可以使用 web workers 達(dá)到相同的效果。你可以閱讀 實時處理應(yīng)用 和 在 angular 中使用 web workers 來了解更多信息。
避免使用大量的內(nèi)聯(lián)腳本
內(nèi)聯(lián)腳本是在主線程中處理的,根據(jù)之前的說法,應(yīng)該盡量避免這樣做。事實上,除了異步和延遲加載之外,任何 JavaScript 的加載都會阻塞主線程。
避免嵌套外層函數(shù)
懶編譯也是發(fā)生在主線程上的。不過,如果處理得當(dāng)?shù)脑?,懶解析可以加快啟動速度。想要強制進(jìn)行全解析的話,可以使用諸如 optimize.js(已經(jīng)不維護(hù))這樣的工具來決定進(jìn)行全解析或者懶解析。
分解超過 100kB 的文件
將大文件分解成小文件以最大化并行腳本的加載速度?!?019 年 JavaScript 的性能開銷”一文比較了 Facebook 網(wǎng)站和 Reddit 網(wǎng)站的文件大小。前者通過在 300 多個請求中拆分大約 6MB 的 JavaScript ,成功將解析和編譯工作在主線程上的占比控制到 30%;相反,Reddit 的主線程上進(jìn)行解析和編譯工作的達(dá)到了將近 80%。
2. 使用 JSON 而不是對象字面量 —— 偶爾
在 JavaScript 中,解析 JSON 比解析對象字面量來得更加高效。 parsing benchmark 已經(jīng)證實了這一點。在不同的主流 JavaScript 執(zhí)行引擎中分別解析一個 8MB 大小的文件,前者的解析速度最高可以提升 2 倍。
2019 年谷歌開發(fā)者大會 也討論過 JSON 解析如此高效的兩個原因:
JSON 是單字符串 token,而對象字面量可能包含大量的嵌套對象和 token;
語法對上下文是敏感的。解析器逐字檢查源代碼,并不知道某個代碼塊是一個對象字面量。而左大括號不僅可以表明它是一個對象字面量,還可以表明它是一個解構(gòu)對象或者箭頭函數(shù)。
不過,值得注意的是,JSON.parse 同樣會阻塞主線程。對于超過 1MB 的文件,可以使用 FlatBuffers 提高解析效率。
3. 最大化代碼緩存
最后,你可以通過完全規(guī)避解析來提高解析效率。對于服務(wù)端編譯來說, WebAssembly (WASM) 是個不錯的選擇。然而,它沒辦法替代 JavaScript。對于 JS,更合適的方法是最大化代碼緩存。
值得注意的是,緩存并不是任何時候都生效的。在執(zhí)行結(jié)束之前編譯的任何代碼都會被緩存 —— 這意味著處理器、監(jiān)聽器等不會被緩存。為了最大化代碼緩存,你必須最大化執(zhí)行結(jié)束之前編譯的代碼數(shù)量。其中一個方法就是使用立即執(zhí)行函數(shù)(IIFE)啟發(fā)式:解析器會通過啟發(fā)式的方法標(biāo)識出這些 IIFE 函數(shù),它們會在稍后立即被編譯。因此,使用啟發(fā)式的方法可以確保一個函數(shù)在腳本執(zhí)行結(jié)束之前被編譯。
此外,緩存是基于單個腳本執(zhí)行的。這意味著更新腳本將會使緩存失效。V8 團(tuán)隊建議可以分割腳本或者合并腳本,從而實現(xiàn)代碼緩存。但是,這兩個建議是互相矛盾的。你可以閱讀“JavaScript 開發(fā)中的代碼緩存”來了解更多代碼緩存相關(guān)的信息。
以上就是“JavaScript的運行原理分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學(xué)習(xí)更多的知識,請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
文章名稱:JavaScript的運行原理分析
文章路徑:http://redsoil1982.com.cn/article46/pdsghg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)網(wǎng)站制作、響應(yīng)式網(wǎng)站、網(wǎng)站排名、域名注冊、電子商務(wù)、企業(yè)建站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)