美文网首页
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