動態載入 script. ie 下 script Element 的 readyState狀態

來源:互聯網
上載者:User

為了 指令碼資源的高並行載入 提高頁面載入速度.. 我們可能需要動態載入 script...     其中總是無法避免的 一個方法  是 使用  head.appendChild(script) ;  因為這種方式 可以直接跨域.

但是有時候  動態載入指令碼可能是要保證 他們的執行時序.   最理想的狀態就是  所有的指令碼 都可以 在 當前  http串連數 允許的前提下.最大化並行載入量的 同時 .. 可以選擇 按時序執行或按載入完成順序執行 即先到先執行.

假設 我們有 3個 js   分別為  a.js  b.js c.js  傳統的方式  是這樣的 :

<script src="a.js"></script>

<script src="b.js"></script>

<script src="c.js"></script>

這樣的載入 大概 只有 ff3.0+ 和opera 的比較新的版本 可以保證 他們的並行載入  而 其他瀏覽器 則只會為他們開啟 一個http串連 . 一個一個的載入.

讓他們在其他瀏覽器並行載入的方式 有 幾種  比如 藉助iframe  比如document.write 比如 上面說到的 head.appendChild(script)  比如 xhr 請求 然後eval . 比如 xhr 注入(即 xhr 和head.appendChild(script) 相結合) .   其中 document.write 寫入指令碼塊.的方式有些問題 其中比較頭疼的 是  有些瀏覽器 可能會 警告 說是 危險代碼 .以及一些其他問題 . 詳細問題 可以去 yslow 作者 blog 去看看.    而另外的 方式 顯然 都不能很方便的解決 跨域問題.   所以我們 來探討下  本文的重點  head.appendChild(script) 方式.

這種方式在非ie下 我們可以 很容易的 捕捉  script.onload 以及 script.onerror  但是 ie 我們只能藉助 script.onreadystatechange 來判斷其載入執行狀態.  而 onerror 則必須 和 被請求的.js檔案 做某些 約定 才可以更好的判斷. 該細節不在本文討論範圍... 

在話題繼續下去前   我們應該先明確 下  .

在動態添加script塊的情況下 為什麼需要 script.onload .     比如 上面的a.js b.js c.js  這三個指令碼  假如他們的執行時序 是有依賴性的 話  那麼我們就必須 保證a.js b.js c.js按次序執行...  但是 一單他們並行載入  的話  只有 firefox 和opera 才可以保證 他們按照請求發起的 順序 來執行這些指令碼. 而其他瀏覽器 則會是 無論誰先向伺服器發起請求.  都只會是一個結果 即 哪個先載入完畢  先執行哪個.  為了 讓我們的Loader API 更健壯... 不得不 在其他瀏覽器下 犧牲 並行載入 這個好處, 而 改用 載入好一個 執行完畢後 再載入下一個的方式來處理... 當然 這裡我們有 另外的解決 方案 留到後面說.    現在 我們把需求簡化一下   我們僅僅想要 script 載入並且 執行完畢後 回調我們指定的方法.

對於非ie  非常簡單  如下面的代碼 :

var script = document.createElement('script');

script.onload=function () {alert('callBack');};

script.src="a.js" ;

document.getElementsByTagName('head')[0].appendChild(script);

就是如此簡單 對吧 . 但是遺憾的是 ie並不支援 script.onload事件  這時候我們 只好藉助

script.onreadystatechange=function(){ script.readyState=='某個值'} 

這種方式來判斷 指令碼是否 載入 並執行完畢

此時 readyState 的值  可能為 以下幾個 :

  • “uninitialized” – 原始狀態
  • “loading” – 下載資料中..
  • “loaded” – 下載完成
  • “interactive” – 還未執行完畢.
  • “complete” – 指令碼執行完畢.

現在 問題來了 . ie6 和 ie7 ie8 有些區別  大致分為以下幾種情況 (以下狀態 本人測試後 又請幾位朋友幫忙測試  應該可以信任.)

script.src="a.js" ;

document.getElementsByTagName('head')[0].appendChild(script);

先寫src 後append 的情況下  complete 和 loaded 只有一個 會出現. 他都標誌著 指令碼載入 並執行完畢   但出現哪個 有時候卻不能確定 和 載入指令碼時間 以及 指令碼執行時間 都有關係.

 

document.getElementsByTagName('head')[0].appendChild(script);

script.src="a.js" ;

先append 後給src 則  比較其開怪 ie 就可能同時 出現 complete 和loaded  而 其中 ie7 和ie8 總能保證 loaded 為 最後一個狀態  即 此時 ie7 8 我們可以信任 loaded 狀態時 指令碼 已經載入 執行完畢 .  但是 ie6 就比較鬱悶 它會 因為 指令碼載入時間 和 指令碼載入後執行時間不同   導致 loaded 和 complete 的出現先後次序 的不同...這時候我們無法得知  哪個狀態 才是 指令碼執行完畢的狀態...    為了判斷 這種狀態下 指令碼是否執行完畢  我們需要藉助 額外的 開關變數來 信任 最後出現的 那個 狀態 才是 指令碼執行完畢的 狀態. 

 

經過大量測試後  得出一個 結論 即  如果 你想少惹麻煩的話  請 先給script 設定 src屬性  然後 再 appedChild 他 到 DOM樹中...  這樣的話 我們 就只需要  這種代碼 即可以 判斷  指令碼執行結束 了.

 

if (/loaded|complete/.test(script.readyState)) //ie6 ie7 ie8 通用.  好了 解決了readyState問題後 我們 去看看另外的問題

 

 

大神  Nicholas C. Zakas 給出的方案如下 :

 

 

view sourceprint?
01 if (script.readyState) { //IE
02     script.onreadystatechange = function(){
03        a.push(script.readyState);
04         if (script.readyState == "loaded" || script.readyState == "complete") {
05             script.onreadystatechange = null;
06             callback && callback();
07         }
08     };
09
10 }
11 else { //Others
12     script.onload = function(){
13         callback();
14     };
15 }

 

很明顯 大神忽略了 兩問題.

1. script.onreadystatechange=function(){} 這種方式 會造成某些版本的ie6無法挽回的 cross page leak 記憶體泄露 (暫時我只能確定可以確定sp3補丁的ie6沒這個問題 ) 所以即使 他具備 script.onreadystatechange = null; 對於ie6沒有意義

2. opera 也支援 readyState這個事實 所以他這段指令碼 在opera比較新的瀏覽器下 就會出問題...    另外 一但 將來的某個ie版本支援了 script.onload 我們可能 就無法享受到這個好處 了

對於1

建議使用attachEvent 請記得 對於ie6 任何非 attachEvent方式註冊的事件 (除寫入程式碼寫到html中的) 都會引起ie6 無法挽回的 跨頁記憶體泄露. 至於多少 就看回呼函數 所在閉包  中的資料量了. 範圍鏈 越深 受影響的東西 就越多...危害也就越大.

對於2

我覺得 還是應該 優先判斷是否支援 onload 才是正確的思路  比如這樣:

view sourceprint?
1 function isImplementedOnload(script){
2   script = script || document.createElement('script') ;
3   if('onload' in script) return true ;
4     script.setAttribute('onload','');
5     return typeof script.onload == 'function' ; // ff true ie false .
6 }

很顯然 一來二去的  隨著我們代碼量的增加 現在 script 塊 動態載入指令碼 顯得越來越靠譜了....

那麼我們說說 應用中的一些問題

理想狀態下  所有指令碼 都應該 最大化的 利用 當前可用的http串連數  即有幾個我就用機個 去並行載入指令碼 而不是一個一個的在那裡阻塞... 但是很顯然 想做到面面俱到 是不容易的事情.

那麼 應該有以下一個 流程來處理他們

1.  優先考慮 xhr eval 或 xhr 注入  這兩種方式 可以在保證並行下載資源的同時  很方便的 控制 執行的時序.

2. 一但無法解決跨域問題  則 使用 script 塊 動態載入的方式  但應知道 此種方式 非ff opera瀏覽器 一旦要求執行時序 則我們 無法達成  並行載入 這一最初的 目的.

3. 群裡的朋友  瓶子 給出的方案 是  可以借用 postMessage    ie6 7使用 window.opener 漏洞  去實現域通訊 然後藉助一個 被引入的iframe頁面 去請求指令碼 然後 把 指令碼內容 傳給父頁

方案3  瓶子給出的demo  : http://www.webairness.com/apps/libs/local2.html

好吧回到最初的話題  請記得 對於動態載入 scirpt塊  仍然可能 阻塞window.onload的情況 當然(硬式編碼 輕輕是一定會阻塞的啦) 我們可以 把請求代碼 寫到 setTimeout  1ms 中  這樣 就可以更早的 window.onload   越早onload的好處 就是 當我們  在 window.onunload 釋放一些 事件偵聽回呼函數 以避免 記憶體泄露時 變的尤其重要  .  因為 有些瀏覽器  onload不發生   就永遠不會發生 onunload  這是個很無奈的問題.... 這也是為什麼推薦少使用iframe去 解決 一些跨域 問題的初衷...因為firame 會阻塞 首頁面的 onload ....

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.