JavaScript >> Javascript 文檔 >  >> JavaScript

關於腳本加載器的思考

上週,Steve Souders 發布了他的 ControlJS 項目。該項目的目標是讓開發人員更好地控制 JavaScript 文件在頁面上加載和執行的方式和時間。它通過使用 Stoyan Stefanov 的預加載 JavaScript 而不執行它的方法來做到這一點,並且具有啟用並行下載的令人愉快的副作用。有關使用的更多詳細信息,請查看 Steve 的三篇博文。

第一篇博文包含來自另一個腳本加載器 LABjs 的創建者 Kyle Simpson 的評論中的一些批評。 LABjs 的目標與 ControlJS 有點不同:在保持執行順序的同時啟用 JavaScript 文件的並行下載。為此,LABjs 需要知道哪些瀏覽器默認允許並行下載,然後為不支持的瀏覽器提供其他解決方案。

LABjs 和 ControlJS 都有一個主要問題:它們使用各種瀏覽器檢測技術來確定正確的操作過程以優化腳本加載。有人認為 LABjs 的瀏覽器推斷比 ControlJS 的用戶代理檢測更安全,但我不同意。瀏覽器推斷是特徵檢測加上假設,是一種固有缺陷的方法(嚴重)。瀏覽器推斷並不比用戶代理檢測更準確,也不太可能失敗。我並不是說用戶代理檢測是一件好事,但至少它在它試圖檢測的內容上是明確的。我每次都選擇顯式而不是隱式,因為它有助於防止錯誤,或者如果發生錯誤,可以更快地識別它們。但這是一個與本文的重點無關的辯論。

LABjs 已經證明,這種基於瀏覽器的腳本加載技術分支是一個壞主意。它太脆弱了,無法承受我們所看到的瀏覽器更新的衝擊,這就是為什麼我從未建議使用試圖超越瀏覽器的腳本加載器。當 Firefox 4 nightlies 開始出現破壞 LABjs 的行為時,Kyle 面臨一個嚴重的問題。問題是動態插入的腳本元素不再保證執行順序,這是 LABjs 所依賴的。所做的更改是為了使 Firefox 與 HTML5 規範和其他瀏覽器保持一致。隨著瀏覽器的不斷發展,ControlJS 無疑會遇到同樣的問題。此類解決方案的維護成本很高。

真正的問題

關於 LABjs 和 ControlJS 試圖解決的真正問題存在一些爭論。事實上,圖書館代表了三個問題。

首先,兩者都在嘗試啟用 JavaScript 資源的並行下載。這是一個有價值的目標,但新的瀏覽器已經在處理這個目標。儘管嘗試在舊瀏覽器中擠出 JavaScript 下載的並行化在學術上很有趣,但我認為這實際上不值得。瀏覽器已經為我們解決了這個問題,因此不需要腳本加載器來提供幫助。

其次,LABjs 非常注重維護腳本執行順序。隨之而來的假設是:您想要下載多個相互依賴的 JavaScript 文件。這是我不推薦的,但我知道有些人覺得這很重要。 ControlJS 不關心這個。無論哪種方式,這是一個瀏覽器沒有以合理的方式處理的問題,所以如果你想要這種行為,你必須使用腳本加載器。

第三,ControlJS 非常注重 JavaScript 下載和執行的分離。內置的想法是,您應該能夠下載 JavaScript 文件並且直到您確定的時間點才執行它。這是一個有趣的概念,並且已經在社區中進行了大量實驗(正如史蒂夫在他的博客文章中指出的那樣)。這裡的假設是您的頁面會逐漸增強,因此不需要立即使用 JavaScript。 LABjs 沒有解決這個問題。瀏覽器也無濟於事。

武裝號召

儘管凱爾和我在很多事情上存在分歧,但我認為他在呼籲為問題 2 提供共同解決方案時說得完全正確。 我們不應該需要腳本加載器 .應該有本地方法來實現開發人員需要和想要對 JavaScript 文件做的所有事情。腳本加載器向我們展示了開發人員試圖解決性能問題的方法,合乎邏輯的下一步是讓瀏覽器供應商內部化這些問題並提出解決問題的方法。 Kyle 對問題和如何解決問題 #2 的建議進行了長時間的檢查(注意:沒有人提出解決問題 #3 的建議)。我承認,Kyle 在此過程中徵求了我的反饋意見,但我非常專注於幾個項目,直到現在都沒有時間真正深入研究。

async=false?

Kyle 提出的一項提議要求對 async 進行奇怪的擴充 <script> 的屬性 標籤。 async attribute 是一個布爾屬性,這意味著它的存在表明該功能應該被打開,這也意味著該屬性值沒有任何意義。所以下面三行是等價的:

<script async src="foo.js"></script>
<script async="true" src="foo.js"></script>
<script async="false" src="foo.js"></script>

這些行為符合 HTML5 的規定:它們立即開始下載,並在下載完成後立即執行,而不保留順序。在 JavaScript 中,您可以通過在腳本元素上設置相應的異步屬性來啟用或禁用此功能:

var script = document.screateElement("script");
script.async = true;   //enable async per HTML

在 Kyle 的提議下,設置 async 腳本元素上的屬性使用 JavaScript 會觸發新模式。所以這段代碼的意思變了:

var script = document.screateElement("script");
script.async = false;   //switch into new mode (WebKit nightly, Firefox 4)

以前,設置 async 為 false 將沒有效果。現在,設置 async 在支持的瀏覽器中設置為 false 使得腳本以非阻塞方式下載,同時保持執行順序。

雖然我讚賞凱爾堅持提出建議,但我對此感到有些困惑。對我來說,這段代碼讀作“這個腳本不是異步的”而不是“這個腳本是異步的,請保持順序”。再一次,為了避免錯誤,我更喜歡顯式而不是隱式。

在他的 twiki 中提到的另一個建議是創建一個 <scriptgroup> 將腳本文件邏輯地組合在一起的元素:

<scriptGroup id="group1" ordered="true">
   <script src="foo.js"></script>
   <script src="bar.js"></script>
   <script>
     somethingInline();
   </script>
 </scriptGroup>

我其實很喜歡這個提議。這是明確的,對於這裡發生的事情幾乎沒有疑問,您可以想像將事件處理程序附加到 <scriptgroup> 可以告訴您何時加載所有文件的元素。它確實引入了另一個元素,但為了清楚起見,我認為這種開銷已通過開發人員意圖的明顯性得到驗證。

單獨下載和執行

仍然沒有很好的、一致的解決方案來分離 JavaScript 的下載和執行,我認為這是非常必要的。這不僅適用於頁面加載時腳本文件的初始加載,還適用於頁面加載後動態添加新代碼。在我的演示文稿中,雅虎上的性能!主頁,我談到了我們如何在頁面加載後在 JavaScript 中涓涓細流,以便在用戶進行其他操作時準備好。預加載 JavaScript 並稍後執行的能力絕對變得越來越重要,而這正是 ControlJS 試圖解決的問題。

在一個理想的世界裡,我可以這樣做:

var script = document.createElement("script");
script.type = "text/cache";
script.src = "foo.js";
script.onload = function(){
    //script has been loaded but not executed
};
document.body.insertBefore(script, document.body.firstChild);

//at some point later
script.execute();

這就是我想要的。我不想發出一個下載文件的請求,然後發出另一個請求,期望該文件在緩存中——這是解決這個問題的一個非常脆弱的解決方案。我想要的是下載文件,把它放在緩存中,然後再調用一個 運行該代碼的方法。這就是 ControlJS 正在建模的內容。

最後

LABjs 和 ControlJS 都試圖以不同的方式解決 JavaScript 加載問題。凱爾和史蒂夫都是聰明人,他們追求解決相似和略有不同的問題的方法。好消息是,我們現在有兩個腳本加載器,它們展示了開發人員嘗試在其頁面上加載腳本的各種方式,希望這足以讓瀏覽器供應商走到一起,就長期的原生解決方案達成一致,這樣我們就贏了。以後不需要腳本加載器了。

在短期內,向 Kyle 和 Steve 道歉,我不推薦使用任何一個。雖然兩者都說明了腳本加載的有趣方法,但對瀏覽器檢測的依賴意味著它們將需要隨著新瀏覽器版本的出現而不斷監控和更新。維護在大型 Web 應用程序中很重要,而這些庫目前會增加不必要的維護開銷。

我知道這是一個最近很熱的話題,所以我會請大家盡量保持你的評論文明。

更新(2010 年 12 月 22 日): 更改了對 async=false 工作方式的描述,因為我的原始示例錯誤地顯示了與標記一起使用的功能,而實際上它僅適用於腳本。


Tutorial JavaScript 教程
  1. 帶有泛型的 TypeScript 實用程序類型

  2. 如何刪除 getbutton.io 品牌

  3. 重疊項目上的點擊事件

  4. React 測試速成班

  5. AJAX - 獲取、發布、放置和刪除

  6. useReducer ReactJS hook 的 2 個用例

  7. 說話的機率是多少?

  1. 如何將元素位置設置為另一個元素位置。

  2. 5 個流行的 Javascript 框架和何時使用

  3. JavaScript 去抖函數

  4. TypeScript 向數組中添加一個或多個元素

  5. Leetcode:兩個數字相加

  6. 如何為同一擴展名的 manifest.json 文件中的多個內容腳本指定不同的匹配模式

  7. ES6 入門

  1. 使用 Webpack 同時為 Web 和 React Native 開發 NPM 模塊

  2. 使用 AXIOS 搜索 Github 用戶

  3. Syncfusion React 甘特圖組件概述

  4. 將 PostgreSQL 數據庫添加到 Heroku 上的 Node.js 應用程序