美文网首页
zepto的内存泄漏

zepto的内存泄漏

作者: luna0607 | 来源:发表于2018-08-16 18:06 被阅读0次

做个小实验:

$("#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. 使用zeptoempty()来删除元素(增长很多)

zepto-empty-heapshot.png

2. 使用zeptoremove()来删除元素(增长很多):$("#content").children().remove();

zepto-remove-heapshot.png

3. 使用jqueryempty()来删除元素(没有什么增长)

jquery-empty-heapshot.png

4. 使用jqueryremove()来删除元素(没有什么增长)

jquery-remove-heapshot.png

可以确定,是zeptoemptyremove没有正确删除元素,造成了内存泄漏。

读读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;
},

jqueryempty函数第7783行给出了官方注释:清除元素节点并阻止内存泄漏。最重要的方法是cleanData

cleanData(代码略)主要方法是,首先删除dataevents,然后删除cache中的data。但是这个方法依赖于 jQuery.expando, jQuery.cachejQuery自有属性,无法全盘照抄。

zepto的事件绑定机制

而zepto中连cache都没有,zepto的逻辑是:用一个handlers对象来处理所有绑定的函数,handlers对象长这个样子,是key-value对的形式,其中key是dom元素的_zid,value是含有函数信息的Array对象。

handlers.png

如果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就存在。

屏幕快照 2018-08-16 下午3.27.03.png

(完)

相关文章

网友评论

      本文标题:zepto的内存泄漏

      本文链接:https://www.haomeiwen.com/subject/dlvobftx.html