一篇文章帶你搞定JavaScript 性能調優

python進階學習交流 發佈 2022-05-03T10:38:48.238194+00:00

大家好,我是皮皮。

大家好,我是皮皮。

JavaScript 是單線程運行的,所以在在執行效率上並不是很高,隨著用戶體驗的日益重視,前端性能對用戶體驗的影響備受關注,但由於性能問題相對複雜,接下來我們來了解下JavaScript如何提高性能;

從加載上優化:合理放置腳本位置

由於 JavaScript 的阻塞特性,在每一個<script>出現的時候,無論是內嵌還是外鏈的方式,它都會讓頁面等待腳本的加載解析和執行,

並且<script>標籤可以放在頁面的<head>或者<body>中,因此,如果我們頁面中的 css 和 js 的引用順序或者位置不一樣,即使是同樣

的代碼,加載體驗都是不一樣的。示例如下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <title>js 引用的位置性能優化</title>
        <script type="text/javascript" src="index-1.js"></script>
        <script type="text/javascript" src="index-2.js"></script>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <div id="app"></div>
    </body>
</html>

以上代碼是一個簡單的 html 界面,其中加載了兩個 js 腳本文件和一個 css 樣式文件,由於 js 的阻塞問題,當加載到 index-1.js 的時候,

其後面的內容將會被掛起等待,直到index-1.js 加載、執行完畢,才會執行第二個腳本文件 index-2.js,這個時候頁面又將被掛起等待腳

本的加載和執行完成,一次類推,這樣用戶打開該界面的時候,界面內容會明顯被延遲,我們就會看到一個空白的頁面閃過,這種體驗是

明顯不好的,因此 我們應該儘量的讓內容和樣式先展示出來,將 js 文件放在 最後,以此來優化用戶體驗。如下所示:

<!DOCTYPE html>
<html>

    <head>
     <meta charset="utf-8">
        <meta name="vIEwport" content="width=device-width,initial-scale=1.0">
        <title>js 引用的位置性能優化</title>
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
     <div id="app"></div>
     <script type="text/javascript" src="index-1.js"></script>
     <script type="text/javascript" src="index-2.js"></script>
    </body>
</html>

這段代碼展示了在 HTML 文檔中放置<script>標籤的推薦位置。儘管腳本下載會阻塞另一個腳本,但是頁面的大部分內容都已經下載完

成並顯示給了用戶,因此頁面下載不會顯得太慢。這是雅虎特別性能小組提出的優化 JavaScript 的首要規則:將腳本放在底部。

從請求次數上優化:減少請求次數

由於每個<script>標籤初始下載時都會阻塞頁面渲染,所以減少頁面包含的<script>標籤數量有助於改善這一情況。這不僅針對外鏈腳本,內嵌腳本的數量同樣也要限制。瀏覽器在解析 HTML 頁面的過程中每遇到一個<script>標籤,都會因執行腳本而導致一定的延時,因此最小化延遲時間將會明顯改善頁面的總體性能。

這個問題在處理外鏈 JavaScript 文件時略有不同。考慮到 HTTP 請求會帶來額外的性能開銷,因此下載單個 100Kb 的文件將比下載 5 個 20Kb 的文件更快。也就是說,減少頁面中外鏈腳本的數量將會改善性能。

通常一個大型網站或應用需要依賴數個 JavaScript 文件。您可以把多個文件合併成一個,這樣只需要引用一個<script>標籤,就可以減少性能消耗。文件合併的工作可通過離線的打包工具或者一些實時的在線服務來實現。

需要特別提醒的是,把一段內嵌腳本放在引用外鏈樣式表的之後會導致頁面阻塞去等待樣式表的下載。這樣做是為了確保內嵌腳本在執行時能獲得最精確的樣式信息。因此,建議不要把內嵌腳本緊跟在標籤後面。

有一點我們需要知道:頁面加載的過程中,最耗時間的不是 js 本身的加載和執行,相比之下,每一次去後端獲取資源,客戶端與後台建立連結才是最耗時的,也就是大名鼎鼎的Http 三次握手,當然,http 請求不是我們這一次討論的主題,因此,減少 HTTP 請求,是我們著重優化的一項,事實上,在頁面中 js 腳本文件加載很很多情況下,它的優化效果是很顯著的。

從加載方式上優化:無阻塞腳本加載

在 JavaScript 性能優化上,減少腳本文件大小並限制 HTTP 請求的次數僅僅是讓界面響應 迅速的第一步,現在的 web 應用功能豐富,js 腳本越來越多,光靠精簡源碼大小和減少 次數不總是可行的,即使是一次 HTTP 請求,但文件過於龐大,界面也會被鎖死很長一段 時間,這明顯不好的,因此,無阻塞加載技術應運而生。簡單來說, 就是 頁面在加載完成後才加載 s js 代碼,也就是在 w window 對象的 d load 事件觸 發後才去下載腳本。要實現這種方式,常用以下幾種方式:

延遲腳本加載( defer )

HTML4 為<script>標籤定義了一個擴展屬性:defer。Defer 屬性指明本元素所含的腳本不會修改 DOM,因此代碼能安全地延遲執行。defer 屬性只被 IE 4 和 Firefox 3.5 更高版本的瀏覽器所支持,所以它不是一個理想的跨瀏覽器解決方案。在其他瀏覽器中,defer 屬性會被直接忽略,因此<script>標籤會以默認的方式處理,也就是說會造成阻塞。然而,如果您的目標瀏覽器支持的話,這仍然是個有用的解決方案。

<script type="text/javascript" src="index-1.js" defer></script>

帶有 defer 屬性的<script>標籤可以放置在文檔的任何位置。對應的 JavaScript 文件將在頁面解析到<script>標籤時開始下載,但不會執行,直到 DOM 加載完成,即 onload事件觸發前才會被執行。當一個帶有 defer 屬性的 JavaScript 文件下載時,它不會阻塞瀏覽的其他進程,因此這類文件可以與其他資源文件一起並行下載。·任何帶有 defer 屬性的<script>元素在 DOM 完成加載之前都不會被執行,無論內嵌或者是外鏈腳本都是如此。

延遲腳本加載( async )

HTML5 規範中也引入了 async 屬性,用於異步加載腳本,其大致作用和 defer 是一樣的,都是採用的並行下載,下載過程中不會有阻塞,但 不同點在於他們的執行時機,c async 需要加載完成後就會自動執行代碼 ,但是 r defer 需要等待頁面加載完成後才會執行。

從加載方式上優化:動態添加腳本元素

把代碼以動態的方式添加的好處是:無論這段腳本是在何時啟動下載,它的下載和執行過程都不會阻塞頁面的其他進程,我們甚至可以直接添加帶頭部 head 標籤中,都不會影響其他部分。因此,作為開發的你肯定見到過諸如此類的代碼塊:

var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'file.js';
document.getElementsByTagName('head')[0].appendChild(script);

這種方式便是動態創建腳本的方式,也就是我們現在所說的動態腳本創建。通過這種方式下載文件後,代碼就會自動執行。但是在現代瀏覽器中,這段腳本會等待所有動態節點加載完成後再執行。這種情況下,為了確保當前代碼中包含的別的代碼的接口或者方法能夠被成功調用,就必須在別的代碼加載前完成這段代碼的準備。解決的具體操作思路是:現代瀏覽器會在 script 標籤內容下載完成後接收一個load 事件,我們就可以在 load 事件後再去執行我們想要執行的代碼加載和運行,在 IE 中,它會接收 loaded 和 complete事件,理論上是 loaded 完成後才會有 completed,但實踐告訴我們他兩似乎並沒有個先後,甚至有時候只會拿到其中的一個事件,我們可以單獨的封裝一個專門的函數來體現這個功能的實踐性,因此一個統一的寫法是:

function LoadScript(url, callback) {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    // IE 瀏覽器下
    if (script.readyState) {
        script.onreadystatechange = function () {
            if (script.readyState == 'loaded' || script.readyState ==
                'complete') {
                // 確保執行兩次
                script.onreadystatechange = null;
                // todo 執行要執行的代碼
                callback()
            }
        }
    } else {
        script.onload = function () {
            callback();
        }
    }
    script.src = 'file.js';
    document.getElementsByTagName('head')[0].appendChild(script);
}

LoadScript 函數接收兩個參數,分別是要加載的腳本路徑和加載成功後需要執行的回調函數,LoadScript 函數本身具有特徵檢測功能,根據檢測結果(IE 和其他瀏覽器),來決定腳本處理過程中監聽哪一個事件。實際上這裡的 LoadScript()函數,就是我們所說的 LazyLoad.js(懶加載)的原型。

從加載方式上優化:XMLHttpRequest 腳本注入

通過 XMLHttpRequest 對象來獲取腳本並注入到頁面也是實現無阻塞加載的另一種方式,這個我覺得不難理解,這其實和動態添加腳本的方式是一樣的思想,來看具體代碼:

var xhr = new XMLHttpRequest();
xhr.open('get', 'file-1.js', true);
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
            // 如果從後台或者緩存中拿到數據,則添加到 script 中並加載執行。
            var script = document.createElement('script');
            script.type = 'text/javascript';
            script.text = xhr.responseText;
            // 將創建的 script 添加到文檔頁面
            document.body.appendChild(script);
        }
    }
}

通過這種方式拿到的數據有兩個優點:其一,我們可以控制腳本是否要立即執行,因為我們知道新創建的 script 標籤只要添加到文檔界面中它就會立即執行,因此,在添加到文檔界面之前,也就是在 appendChild()之前,我們可以根據自己實際的業務邏輯去實現需求,到想要讓它執行的時候,再 appendChild()即可。其二:它的兼容性很好,所有主流瀏覽器都支持,它不需要想動態添加腳本的方式那樣,我們自己去寫特性檢測代碼;但由於是使用了 XHR 對象,所以不足之處是獲取這種資源有「域」的限制。資源 必須在同一個域下才可以,不可以跨域操作。

總結

減少 JavaScript 對性能的影響有以下幾種方法:

  • 將所有的<script>標籤放到頁面底部,也就是</body>閉合標籤之前,這能確保在 腳本執行前頁面已經完成了渲染。
  • 儘可能地合併腳本。頁面中的<script>標籤越少,加載也就越快,響應也越迅速。無論是外鏈腳本還是內嵌腳本都是如此。
  • 採用無阻塞下載 JavaScript 腳本的方法:
    • 使用<script>標籤的 defer 屬性(僅適用於 IE 和 Firefox 3.5 以上版 本);
    • 使用動態創建的<script>元素來下載並執行代碼;
    • 使用 XHR 對象下載 JavaScript 代碼並注入頁面中。

通過以上策略,可以在很大程度上提高那些需要使用大量 JavaScript 的 Web 網站和應用的實際性能。

小夥伴們,快快用實踐一下吧!如果在學習過程中,有遇到任何問題,歡迎加我好友,我拉你進Python學習交流群共同探討學習。

關鍵字: