论坛首页 Web前端技术论坛

动态加载Javascript会导致内存泄露?

浏览 8777 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-08-31   最后修改:2009-09-01

 

     以前写过一点关于动态加载js的东东,而上次自己在看到一篇老外的文章后再次记录了点东西,不过昨天中午看到另一篇老外的博客的时候发现说会引起“内存泄露”!自己感到非常惊讶,现在几乎都是这样动态加载外部的js文件啊(包括流行的一些Javascript库,比如YUI get()),可能方式不一样,原理却都差不多啊

       原文在这里:“http://neil.fraser.name/news/2009/07/27/”,赶紧继续往下看,可惜自己英语确实不是很好,很多单词不认识,汗

 

       大意是:利用Script标签可以跨域加载并运行一段JavaScript脚本:

 

script = document.createElement('script');
script.src = 'http://example.com/cgi-bin/jsonp?q=What+is+the+meaning+of+life%3F';
script.id = 'JSONP';
script.type = 'text/javascript';
script.charset = 'utf-8';
var head = document.getElementsByTagName('head')[0];
head.appendChild(script);
 

   通过url传递的是Javascript数据格式,即json。如果我们一次加载的数据量太多,可以分开来加载。这种方式如果是运行一两次,没有任何问题,但是当重复很多次后,head的头部会“充斥”很多script标签,所以我们需要删除旧的标签:

 

var script;
while (script = document.getElementById('JSONP')) {
		script.parentNode.removeChild(script);
}

 作者在这里指出了一个问题:这种方式可以成功的从dom树中删除script标签,可是,问题在于当你清除这些script标签的时候,当今的几乎所有浏览器仅仅是移除该标签结点,而没有对节点内的资源进行垃圾回收。所以,作者在这里移除Script标签后,为了释放脚本资源,还手动清除script标签结点的内容:

 

var script;
while (script = document.getElementById('JSONP')) {
		script.parentNode.removeChild(script);
		for (var prop in script) {
			delete script[prop];
		}
}

 demo无真相,所以,在此处,我还是希望用代码来验证一下作者的观点,按照作者的思路,我写了两页页面,代码如下:

 Code1:

 

var script;
while (script = document.getElementById('JSONP')) {
	alert(script)
	script.parentNode.removeChild(script);
	for (var prop in script) {
		delete script[prop];
	}
	alert(script.id);
	alert(script["src"]);
}

 Code2:

 

var script;
while (script = document.getElementById('JSONP')) {
	script.parentNode.removeChild(script);
	alert(script.id);
	alert(script["src"]);
}

       两个页面的结果是一样,都是先后弹出“JSONP”、“little.js”,而且,测试一下delete运算的返回值,结果为“true”,表示删除成功(注:对象属性不存在时也返回true。但此处很显然属性时存在的。故能根据返回值判断删除是否成功)。这个我就搞不明白了,而更有意思的是:测试页面1(即代码1)在安装了Leak Monitorfirefox下面竟然还提示“内存泄露”的错误!

 

IE下面,测试页面1报错,原因是delete导致了错误!

     我不知道为什么会出现这样的结果,所以我对作者的观点至今抱有怀疑的态度,希望在这里得到大牛的解答~

   发表时间:2009-08-31   最后修改:2009-08-31
标签内的代码已经被浏览器解释执行了,清楚标签并不能清楚已经执行所产生的对象,
要想彻底删除只能手动对标签执行产生的对象进行清除,

可以看一下:
http://yiminghe.iteye.com/blog/419200
0 请登录后投票
   发表时间:2009-09-03  
Neil Fraser说的并非标签内的代码会导致内存泄漏,而是这个script标签
ie下不允许delete原生对象的属性,他给出的解决办法是,不删除script,仅使用一个script.
0 请登录后投票
   发表时间:2009-09-03   最后修改:2009-09-03

我觉得那篇老外文章里唯一可取的是关于ie允许复用同一个script标签的那段…………

对于需要频繁动态加载的script(比如跨域的jsonp接口),可以对ie做一下专门的优化,因为ie里dom是c++写的,好像属于windows网络堆栈的那个部分,跟jscript引擎的垃圾回收机制完全不同,内存泄漏问题比较明显……

刚好我前些天在做一个叫豆丸的项目:douwan.tudou.com,里面需要频繁的polling一个json接口,所以专门写了一个getJSON方法,对ie做一个基本的优化:给相同的请求关联一个特殊id的script,load完之后不remove它,目前看来这种做是没什么问题,不过能少占用多少内存……还需要进一步测试……

 

TUI.getJSON = (function($, TUI){
	var jsonp = {};
	return function(url, fn, op){
		var domain = url.match(/http:\/\/(.+?)\//);
		if (!domain || domain[1] === window.location.host) {
			$.getJSON(url, fn);
			return true;
		}
		op = $.extend({
			charset: "gbk",
			callback: "jsonp" + +new Date()
		}, op || {});
		var random = op.random ? ( "&" + op.random + "=" + +new Date() ) : "";
		url = [url, /\?/.test(url) ? "&" : "?", "jsoncallback=", op.callback, random].join("");
		if (fn)
			TUI.namespace(op.callback, fn);
		var s, h;
		if (op.repeat && $.browser.msie) { //在ie里,对多次请求使用同一个script,避免内存泄漏
			var sid = jsonp[domain[1]];
			if (!sid) {
				sid = jsonp[domain[1]] = "tui_jsonp" + +new Date();
				s = document.createElement("script");
				s.id = sid;
				s.type = "text/javascript";
				s.charset = op.charset;
				s.src = url;
				h = document.getElementsByTagName("head")[0];
				h.appendChild(s);
			} else {
				s = document.getElementById(sid);
				s.charset = op.charset;
				s.src = url;
			}
		} else {
			s = document.createElement("script");
			s.type = "text/javascript";
			s.charset = op.charset;
			s.src = url;
			h = document.getElementsByTagName("head")[0];
			var done = false;
			s.onload = s.onreadystatechange = function(){
				if ( !done && (!this.readyState ||
						this.readyState == "loaded" || this.readyState == "complete") ) {
					done = true;
					//防止ie内存泄漏
					s.onload = s.onreadystatechange = null;
					h.removeChild(s);
					//减少非ie浏览器的内存泄漏
					//if (op.repeat && !$.browser.msie) {
						//for (var prop in s) 
							//delete s[prop];
					//}
				}
			};
			h.appendChild(s);
		}
	};
})(jQuery, TUI);
 
0 请登录后投票
   发表时间:2009-09-03  
lixinlixin2008 写道
Neil Fraser说的并非标签内的代码会导致内存泄漏,而是这个script标签
ie下不允许delete原生对象的属性,他给出的解决办法是,不删除script,仅使用一个script.

建议写页面去测试一下,个人感觉作者的观点不一定就是正确的,我用delete删除发现还是有泄漏的问题

 

0 请登录后投票
   发表时间:2009-09-03  
dexter_yy 写道

我觉得那篇老外文章里唯一可取的是关于ie允许复用同一个script标签的那段…………

对于需要频繁动态加载的script(比如跨域的jsonp接口),可以对ie做一下专门的优化,因为ie里dom是c++写的,好像属于windows网络堆栈的那个部分,跟jscript引擎的垃圾回收机制完全不同,内存泄漏问题比较明显……

刚好我前些天在做一个叫豆丸的项目:douwan.tudou.com,里面需要频繁的polling一个json接口,所以专门写了一个getJSON方法,对ie做一个基本的优化:给相同的请求关联一个特殊id的script,load完之后不remove它,目前看来这种做是没什么问题,不过能少占用多少内存……还需要进一步测试……

 

TUI.getJSON = (function($, TUI){
	var jsonp = {};
	return function(url, fn, op){
		var domain = url.match(/http:\/\/(.+?)\//);
		if (!domain || domain[1] === window.location.host) {
			$.getJSON(url, fn);
			return true;
		}
		op = $.extend({
			charset: "gbk",
			callback: "jsonp" + +new Date()
		}, op || {});
		var random = op.random ? ( "&" + op.random + "=" + +new Date() ) : "";
		url = [url, /\?/.test(url) ? "&" : "?", "jsoncallback=", op.callback, random].join("");
		if (fn)
			TUI.namespace(op.callback, fn);
		var s, h;
		if (op.repeat && $.browser.msie) { //在ie里,对多次请求使用同一个script,避免内存泄漏
			var sid = jsonp[domain[1]];
			if (!sid) {
				sid = jsonp[domain[1]] = "tui_jsonp" + +new Date();
				s = document.createElement("script");
				s.id = sid;
				s.type = "text/javascript";
				s.charset = op.charset;
				s.src = url;
				h = document.getElementsByTagName("head")[0];
				h.appendChild(s);
			} else {
				s = document.getElementById(sid);
				s.charset = op.charset;
				s.src = url;
			}
		} else {
			s = document.createElement("script");
			s.type = "text/javascript";
			s.charset = op.charset;
			s.src = url;
			h = document.getElementsByTagName("head")[0];
			var done = false;
			s.onload = s.onreadystatechange = function(){
				if ( !done && (!this.readyState ||
						this.readyState == "loaded" || this.readyState == "complete") ) {
					done = true;
					//防止ie内存泄漏
					s.onload = s.onreadystatechange = null;
					h.removeChild(s);
					//减少非ie浏览器的内存泄漏
					//if (op.repeat && !$.browser.msie) {
						//for (var prop in s) 
							//delete s[prop];
					//}
				}
			};
			h.appendChild(s);
		}
	};
})(jQuery, TUI);
 

用到了jQuery框架,呵呵,代码写的不错

不过IE确实很让人抓狂了

0 请登录后投票
   发表时间:2009-09-06  
clone168 写道
lixinlixin2008 写道
Neil Fraser说的并非标签内的代码会导致内存泄漏,而是这个script标签
ie下不允许delete原生对象的属性,他给出的解决办法是,不删除script,仅使用一个script.

建议写页面去测试一下,个人感觉作者的观点不一定就是正确的,我用delete删除发现还是有泄漏的问题

 

前段时间做一个项目是: http://imgdb.mapenjoy.com  会很频繁的请求json,我测试拖动地图一段时间后内存会有轻微的上涨...也不知道是不是script引起的内存泄漏...因为程序比较复杂,   不过看起来即使泄漏,泄漏的也很轻微...

 

有机会测试测试吧...有空可否把你的测试代码发给俺呢

0 请登录后投票
   发表时间:2009-09-07   最后修改:2009-09-07
lixinlixin2008 写道
clone168 写道
lixinlixin2008 写道
Neil Fraser说的并非标签内的代码会导致内存泄漏,而是这个script标签
ie下不允许delete原生对象的属性,他给出的解决办法是,不删除script,仅使用一个script.

建议写页面去测试一下,个人感觉作者的观点不一定就是正确的,我用delete删除发现还是有泄漏的问题

 

前段时间做一个项目是: http://imgdb.mapenjoy.com  会很频繁的请求json,我测试拖动地图一段时间后内存会有轻微的上涨...也不知道是不是script引起的内存泄漏...因为程序比较复杂,   不过看起来即使泄漏,泄漏的也很轻微...

 

有机会测试测试吧...有空可否把你的测试代码发给俺呢

我的测试页面以及测试结论你可以到这里看看:http://hi.baidu.com/hulk168/blog/item/3e55488f07ddf8f3513d92f7.html

0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics