JS - 文档的监听器太多了?

时间:2017-10-02 19:43:24

标签: javascript javascript-events garbage-collection event-listener

设置

我有一个可重复使用的自定义下拉菜单,粗略的框架类似于:

var List = (function() {

    // Custom list prototype stuffs...

    function ListObj(el, options) {     
        this._init(el);
    }

    ListObj.prototype._init = function(el) {
        var self = this;
        self.menu = el;
    }   

    // Expose an init function
    return {
        init : function(el, options) {
            new ListObj(el, options);
        }
    };

})();

每个菜单都初始化为:

var ddlist = document.querySelector('.ddlist');
DDList.init(ddlist, options);

这一切都按预期工作。

我想要什么

我希望每个菜单都有一个行为是自动最小化如果发生不在菜单本身内的点击。

我做了什么

我没有为文档单击监听器,而是必须添加每个菜单并检查点击的位置,我决定在上面的_init方法中添加文档点击监听器:

ListObj.prototype._init = function(el) {
    var self = this;
    self.menu = el;

    document.addEventListener('click', function(e) {
        var el = e.target;

        if(self.menu === el || self.menu.contains(el)) {
            // Click was inside menu
            // Perform whatever tasks
        }else {
            // Click was outside of (this) menu, so minimize
            self.menu.minimize();
        }
    });

}

这样,每个单独的菜单都会自动初始化,并显示监控文档点击的行为,如果点击不在菜单中,则最小化,或执行所需的任何任务。

这完美无缺。我特别感兴趣的是,我可以动态创建新的下拉菜单而无需将它们添加到文档点击监听器中,而且我不必与event.stopPropagation();打架(我宁愿切断我的脚)。

但是......我的电脑会爆炸吗?

这适用于单页webapp,这意味着可以创建(并删除)许多数十或数百个这样的菜单。我担心的是所有这些文档点击监听器都会堆积起来并导致性能问题。

如果我做某事......

document.getElementById('someMenu').remove();
JS垃圾收集会知道它可以取消一个文件点击监听器吗?或者听众会坚持到几天结束?如果是后者,有什么方法可以在删除菜单时删除该特定的监听器吗?

一个重要的空洞是菜单可能永远不会被删除,而是它的父节点将被移除 - 因此.remove()将永远不会直接作用于菜单。

非常感谢!

刚刚测试过......

我创建了两个菜单,然后删除了其中一个(.remove())。即使删除了一个菜单,每次点击仍会触发文档点击监听器。这似乎表明垃圾收集将处理这个混乱,我将最终有无数的听众。

那么,现在呢?

1 个答案:

答案 0 :(得分:1)

为函数指定一个名称,这样就可以删除事件监听器。

ListObj.prototype._init = function(el) {
    var self = this;
    self.menu = el;
    self.click_handler = function(e) {
        var el = e.target;
        if (!document.contains(self.menu)) { // This element has been removed from DOM
            document.removeEventListener("click", self.click_handler);
            return;
        }
        if(self.menu === el || self.menu.contains(el)) {
            // Click was inside menu
            // Perform whatever tasks
        }else {
            // Click was outside of (this) menu, so minimize
            self.menu.minimize();
        }
    };
    document.addEventListener("click", self.click_handler);

}