JSONP Memory Leak
前言
JSONP是一种常用的跨域请求脚本的方式。但是当页面使用到轮询时,需要额外小心由此带来的内存泄漏!
如果页面不涉及轮询,那也不是什么大问题。但是当页面中存在轮询跨域请求时,问题就被无数倍的放大了。
当然毫无疑问,IE系列始终是最让人纠结的。
加载脚本
首先,来看看加载脚本的方式:
var script = document.createElement('script');
script.src = 'http://www.abc.com/somepage?callback=check';
script.id = 'JSONP';
var head = document.getElementsByTagName('head')[0];
head.appendChild(script);
如果只是这样增加节点而不删除,由于轮询导致DOM开销不断增长,内存消耗也不断增长。
如果请求的脚本加载了较多内容,那么问题就会更加明显。
读者可以尝试向页面不断载入jquery源代码,设定轮询间隔为2s,可以看到内存的增速达到几十M每秒。
所以十分有必要在脚本执行完成以后删除这些节点。删除脚本
很显然这样的方式不行,为什么不行是因为脚本还没有来得及执行,节点就被删除了。var script = document.createElement('script');
script.src = 'http://www.abc.com/somepage?callback=check';
script.id = 'JSONP';
script.type = 'text/javascript';
script.charset = 'utf-8';
var head = document.getElementsByTagName('head')[0];
head.appendChild(script);
head.removeChild(script);
那就先让脚本执行一会儿:
setTimeout(function(){head.removeChild(script);},200);
不得不说,这样的方式很挫,要是脚本200ms内还没有被执行完成,这样肯定会出问题。
所以需要保证脚本执行完之后自动删除。
幸好IE支持onreadysctatechange,而标准支持onload来判断脚本的执行状态。
var script = document.createElement('script');
head.appendChild(script);
if(script.readyState){
script.onreadysctatechange =function(){
//注意使用this避免内存泄漏
this.onload = null;
this.parentNode.removeChild(this);}
}else{
script.onload =function(){
//注意使用this避免内存泄漏
this.onload = null;
this.parentNode.removeChild(this);
};
}
script.src = url;
IE的问题
其实即使是这样也不能完全避免内存泄漏,标准浏览器包括Chrome随着轮询的都会有内存泄漏的现象,
不过泄漏程度微乎其微(轮询间隔2m,泄漏速度也只有4k-8k)。
但是IE会有十几到几十K的内存泄漏,所以需要对IE进行特殊照顾,这个时候我们还是得借助各个浏览器对script标签的解释。
重用Script标签
标准浏览器对script标签的处理就是每个script标签的地址(src)只能设置一次,
后续的设置虽然能改变script标签的地址但是脚本内容不会执行,不管script标签是页面预留的还是动态插入。
IE下面则很神奇,上面的规则只适合页面上预留的script标签,使用js动态插入的script标签不遵守这一规则。
于是我们只需要动态的插入一个id已知的script标签,然后不断地改变它的src,以此方式加载的脚本都会执行。
而且这个script节点也不必删除,下次请求继续重用即可。
var i = 0;
function _(id){return document.getElementById(id);}
_.isIE = window.ActiveXObject?true:false;
_.ajax = function(){};
_.ajax.jsonp = function(url){
var script, head = document.head || document.getElementsByTagName('head')[0], scriptId = 'ie_script_for_jsonp';
url += (url.indexOf('?')>-1?'×tamp=':'?timestamp=') + new Date().getTime();
//如果是IE浏览器,动态生成script标签,改变src(此方式对标准浏览器无效,预留script标签也无效)
if(_.isIE){
script= document.getElementById(scriptId);
if(!script){
script = document.createElement('script');
script.id = scriptId;
//如果是IE此节点不能删除
head.appendChild(script);
} }else{
script = document.createElement('script');
head.appendChild(script);
script.onload =function(){
this.onload = null;
//删除此节点
this.parentNode.removeChild(this);
};
script.src = url;
}
};
结局
对上述代码测试,使用sieve和任务管理器,发现此方法引起的内存泄漏和chrome下面的差不多都在4K左右,测试环境是IE 8。测试代码结构如下:
window.onload = function(){
setInterval(function(){
_.ajax.jsonp('123.js');
},1000);
};
123.js的内容
i++;
后续
其实传统的方式之所以存在内存泄漏是因为IE的removeChild方法存在内存泄漏的问题,这个jQuery的empty方法已经考虑到。但是为什么jQuery的getJSON在进行跨域请求时仍然存在比较严重的内存泄漏,这个我就不清楚了,有时间探究下源代码。关于removeChild请看这里
更新说明:
上述重用标签的方法对IE 9不适用