利用onerror/onload写的jsonp在IE下不兼容的问题

某一天,我用 js 写了一段 jsonp 的代码。以前在学校也这么写,因为在学校没有测试IE所有的版本兼容性,一直觉得这段代码木有问题。

代码是这样子的:

简单来说就是将一个script标签添加进入dom,这样就可以伪造一次请求,因为同源策略可以用script/image之类标签回避掉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Util.prototype.loadScript = function (url, params, cb) {
var path = url + '?' + this.serializeParam(params);
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
var format = params.format.substr(6), result;

window[format] = function (_res) {
result = _res;
};

var handler = function () {
if(!script) return;

head.removeChild(script);
script = null;
window[format] = undefined;
typeof cb == 'function' && cb(result);
};

script.src = path;
script.async = true;
script.charset = 'utf-8';
script.type = 'text/javascript';


script.onload = handler;
script.onerror = handler;

head.insertBefore(script, head.firstChild);
};

直到测试同事那天在测试的时候,发现IE7,IE6的时候,请求可以发送,但是 callback 函数没有执行。导致了一个按钮好像像点不动一样。

这种 bug 算得上是严重的 bug 了,经过排查,发现确实是代码不完善,没有考虑到:

在多数浏览器(包括Firefox和Chrome)下会触发onload和onerror, 但是在IE下只会触发 onreadystatechange,也就是说在IE8及IE8以下,onerror和onload都不能够使用。

所以要调用回调函数一定需要再写一个针对 onreadystatechange 时的操作。错误中成长。

1
2
3
4
5
6
script.onreadystatechange = function() {
var r = script.readyState;
if (r === 'loaded' || r === 'complete') {
handler();
}
};

淘宝率先不支持IE8及以下,真的是一件美好的事情

完整的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Util.prototype.loadScript = function (url, params, cb) {
var path = url + '?' + this.serializeParam(params);
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
var format = params.format.substr(6), result;

window[format] = function (_res) {
result = _res;
};

var handler = function () {
if(!script) return;

head.removeChild(script);
script = null;
window[format] = undefined;
typeof cb == 'function' && cb(result);
};

script.src = path;
script.async = true;
script.charset = 'utf-8';
script.type = 'text/javascript';

script.onreadystatechange = function() {
var r = script.readyState;
if (r === 'loaded' || r === 'complete') {
handler();
}
};

script.onload = handler;
script.onerror = handler;

head.insertBefore(script, head.firstChild);
};