做个小实验:
$("#button").on("click", appendAndEmpty)
function appendAndEmpty() {
for (let i = 0; i < 100; i++) {
let div = $("<div></div>")
div.on("click", function() {
alert("这是一个弹窗。")
})
$("#content").append(div);
}
$("#content").empty();//用zepto的epmty()方法来清除节点
}
1. 使用zepto
的empty()
来删除元素(增长很多)
2. 使用zepto
的remove()
来删除元素(增长很多):$("#content").children().remove();
3. 使用jquery
的empty()
来删除元素(没有什么增长)
4. 使用jquery
的remove()
来删除元素(没有什么增长)
可以确定,是zepto
的empty
和remove
没有正确删除元素,造成了内存泄漏。
读读empty的源代码:
zepto1.2
(line:561)
empty: function(){
return this.each(function(){ this.innerHTML = '' })
},
jquery1.10.2:
(line:7777)
empty: function() {
var elem,
i = 0;
for (;
(elem = this[i]) != null; i++) {
// Remove element nodes and prevent memory leaks
if (elem.nodeType === 1) {
jQuery.cleanData(getAll(elem, false));
}
// Remove any remaining nodes
while (elem.firstChild) {
elem.removeChild(elem.firstChild);
}
// If this is a select, ensure that it displays empty (#12336)
// Support: IE<9
if (elem.options && jQuery.nodeName(elem, "select")) {
elem.options.length = 0;
}
}
return this;
},
jquery
的empty
函数第7783行给出了官方注释:清除元素节点并阻止内存泄漏。最重要的方法是cleanData
。
cleanData
(代码略)主要方法是,首先删除data
的events
,然后删除cache
中的data
。但是这个方法依赖于 jQuery.expando
, jQuery.cache
等jQuery
自有属性,无法全盘照抄。
zepto的事件绑定机制
而zepto中连cache
都没有,zepto的逻辑是:用一个handlers
对象来处理所有绑定的函数,handlers
对象长这个样子,是key-value对的形式,其中key是dom元素的_zid
,value是含有函数信息的Array对象。
如果dom元素没有绑定事件,那么它没有_zid
属性。
zepto
删除元素时确实把dom元素删除了,但是它上面绑定的事件仍然好好的呆在handlers
里,静静地被人遗忘。
根据这个思路,我写了一个递归删除绑定事件的函数,主要思路是:先获得要删除元素的_zid
,然后去handlers里删除。
function traveseDeleteHandlers(roots) {
for (var i=0;i<roots.length;i++) {
if (roots[i].nodeType == 1 || roots[i].nodeType == 9) {
if(typeof roots[i]!="undefined"&&roots[i].hasOwnProperty("_zid")){
delete handlers[roots[i]._zid]
}
var child = $(roots[i]);
if (child.children().length !== 0) {
traveseDeleteHandlers(child.children())
}
}
}
}
并新增了两个函数:
$.fn.empty = function () {
traveseDeleteHandlers(this.children())
console.log(handlers)
return this.each(function () { this.innerHTML = '' })
}
$.fn.remove =function(){
if(typeof this[0]!="undefined"&&this[0].hasOwnProperty("")){
delete handlers[this[0]._zid];
}
traveseDeleteHandlers(this.children())
console.log(handlers)
return this.each(function(){
if (this.parentNode != null)
this.parentNode.removeChild(this)
})
}
可以解决这个问题。
已向zepto提交了pull request,另外需要说明,zepto的贡献规则是:末尾不可以有分号,也不支持let。地址:https://github.com/madrobby/zepto/pull/1345
前:(垃圾回收后nodelist和listener仍然居高不下)
屏幕快照 2018-08-15 下午3.49.19.png后:(垃圾回收后释放空间)
屏幕快照 2018-08-15 下午3.49.54.png就是不知道算不算优雅,毕竟好像修改源文件很菜,但是这个问题又不能不基于zepto来修改。
补充:昨天给lw看了,他提问说为什么在使用原本的empty()
方法的时候,不仅listener
没有被回收,nodes
也没有被回收?
原因是,handler.proxy
构建的时候是一个闭包(从995行到1019行是构建handler
),element
被作为handler.proxy
的外部参数保存起来。只要handler
存在,element
就存在。
(完)
网友评论