在做加載器時遇到一個常見問題,如何判定一個腳本已經執行完畢。
- “uninitialized” – 原始狀態
- “loading” – 下載數據中
- “loaded” – 下載完成
- “interactive” – 還未執行完畢
- “complete” – 腳本執行完畢.
網上流行的答案是這個,我怎么覺得其實這是抄自XMLHttpRequest的readyState呢?!恰逢這兩個都有這屬性。
我們親自做一個實驗:
<!DOCTYPE html> <html> <head> <title>node.readyState</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <script> var node = document.createElement("script") node.onreadystatechange = function() { var state = node.readyState setTimeout(function() { var div = document.createElement("div") document.body.appendChild(div) div.innerHTML = state }, 300) } var head = document.getElementsByTagName("head")[0] head.appendChild(node) node.src = "avalon.js" </script> </head> <body> <div>node.readyState</div> </body> </html>
完整的控件教程 | |
---|---|
IE11 | 空白,說明完全與標准一致了 |
IE10 | loading loaded |
IE9 | loading loaded |
IE8 | complete loaded |
IE7 | complete loaded 但有一定機率,只出現complete或loaded |
IE6 | complete loaded 但有一定機率,只出現complete或loaded |
換言之,IE67是個非常悲催的問題。另外,opera9-10也支持readyState,根據老外的描述,它竟然兩次都是loaded!
因此我們需要根據瀏覽器的情況采用不同的策略。
首先是使用何種回調,如果是支持onload事件,那么就直接用onload 就沒有這么多麻煩事。最簡單的策略是這樣判定:
var node = DOC.createElement("script") var supportLoad = "onload" in node var onEvent = supportLoad ? "onload" : "onreadystatechange" node[onEvent] = callback
判定完成時機, 我們不使用網上的/complete|loaded|undefined/.test(node.readyState),這會同時掉進opera與IE67的坑中。對於使用onload事件進行監聽的,不再判定node.readyState,IE(其實也就是IE6-8),需要使用一個定時器。當第一次進行onreadystatechange回調時,timeID為空, 並且readyState為complete或loaded時,我們設置它在300ms后再執行自身。然后如果瀏覽器還執行此回調時, 它就進入第二個分支,清掉定時器,執行用戶代碼。萬一,瀏覽器只執行一次onreadystatechange回調,那也沒關系,讓定時器100~300ms后執行用戶代碼
最后貼出全部代碼:
//通過script節點加載目標模塊 var node = DOC.createElement("script") var timeID var supportLoad = "onload" in node var onEvent = supportLoad ? "onload" : "onreadystatechange" node[onEvent] = function onLoad() { if (!supportLoad && !timeID && /complete|loaded/.test(node.readyState)) { timeID = setTimeout(onLoad) return } if (supportLoad || timeID) { clearTimeout(timeID) //你的代碼 } } head.insertBefore(node, head.firstChild) //chrome下第二個參數不能為null node.src = url //插入到head的第一個節點前,防止IE6下head標簽沒閉合前使用appendChild拋錯
大家也可以到這里看一下它的實際應用,如果大家都是使用AMD規范定義JS文件,那么我在舊式IE下連onerror也模擬出來了。