在做加载器时遇到一个常见问题,如何判定一个脚本已经执行完毕。
- “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也模拟出来了。