Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

增加一个DOM事件模块 #6

Closed
otakustay opened this issue Mar 28, 2013 · 13 comments
Closed

增加一个DOM事件模块 #6

otakustay opened this issue Mar 28, 2013 · 13 comments

Comments

@otakustay
Copy link
Member

在控件的开发中,免不了要往main元素上加事件,也免不了要在dispose()的时候把这些事件去掉。

但是这里存在几个问题:

  1. 往往注册上去的事件执行时,我们希望在事件的处理函数内部可以访问到控件的实例(如通过this或者me),这就必然导致事件的处理函数是一个在闭包内的函数(为了固定this或能引用me),也进一步导致,在取消这些事件的时候,容易找不回那个处理函数(因为在闭包里)不好取消。
  2. 在控件的整个生命周期和交互过程中,会不限量地注册各种事件,要在dispose()的时候一一去掉,就要记住注册了哪些事件,用哪些处理函数,这些逻辑全部用代码来写而不抽象出来早晚会混乱。

当然一个非常简单的方法,就是参考现在的controlHelper模块,用onxxx属性来挂事件,到时候只要onxxx = null;就能去掉,但这不仅仅受到 一个事件只能注册一个函数 的限制,同时也只解决了问题1,没解决问题2。看看controlHelper中的initMouseBehaviordispose中的处理,并不是十分潇洒漂亮的形式,这也仅仅是限定地处理了鼠标相关的,有更多事件的话会更难管理。

因此,建议在控件上(事实上是controlHelper模块)中添加一个专门负责main对象上的事件处理,通过addDOMEventremoveDOMEventclearDOMEvents等方法来管理DOM事件,保证注册上就能去掉,而且在dispose()的时候可以去干净。

如果大家认为确实可以有这样一个模块的话,我会负责实现之。

@errorrik
Copy link

我觉得可以有,需要考虑如何设计和组织更好。

假设addDOMEvent(control, element, eventType, listener),那么,control上就需要一个private的属性保存这些个listener,这些listener需要和element对应,那你可能要为没有id的element生成id。

@otakustay
Copy link
Member Author

addDOMEvent(control, type, listener),只管main上的,控件设计也同样追求只用delegate不在具体元素上注册事件,这样如何?

@errorrik
Copy link

控件设计也同样追求只用delegate不在具体元素上注册事件

这个,基本上,很难。。。delegate只在click时算比较好用。我觉得,如果要搞,就搞addDOMEvent,不能只搞addMainEvent

并且,如果只搞main,现在的直接onclick=是好用的。而且在mousemove时性能更高

@otakustay
Copy link
Member Author

onclick=的问题是只能挂一个,你看如果我是控件作者,没看仔细你的helper中有把onmouseup给用了(我也不该知道我的基类竟然有用过这东西),我在我的某个控件里又来个onmouseup=xxx,这就出问题了

我原本的设想是,要给某个子元素注册事件的话,先在子元素上挂个data-ui-role属性,然后提供的注册事件的方法是addDOMEvent(control, type, role, listener),有个自动根据role过滤的功能,当然这种约束对控件开发者来说并不是那么爽快的事,缺点确实如 @errorrik 所说,像mouseenter这种人家压根不冒泡你怎么玩

要搞addDOMEvent也没问题,无非是jQuery那一套,在DOM上直接挂个属性就行,不需要一定有id的。我们不像jQuery还有namespace event之类的,只搞精简的一套也就几十行代码,挺方便的,如果就这么定了的话,明天我搞定之

@errorrik
Copy link

现有实现为main直接onclick=的原因其实是:不希望开发者继续往main上挂这几个事件。不过要改成addListener也行。

role确实是不太容易玩,理解难度有点大。

要搞addDOMEvent也没问题,无非是jQuery那一套,在DOM上直接挂个属性就行,不需要一定有id的

这种搞法坏处有两个:

  1. 卸载时的性能
  2. 卸载时我猜会从main开始找,但是如果元素不在main下(比如现在Table的表头跟随),就卸不掉了

@otakustay
Copy link
Member Author

每个元素上的事件都会丢在控件的一个私有属性里面保存着,卸载的时候把这个属性跑一遍就能全部搞定。每个事件注册时,在这个属性里都会保存elementtypefn等,卸载的时候拿出来直接removeEventListener就行了,最后再把这个属性里的element弄掉防内存泄露,所以我觉得不会产生(无法接受的)性能问题,也不会卸不掉

function triggerDOMEvent(e) {
    var queue = this.domEvents[e.target['__ui_event_identity__']][e.type];
    for (var i = 0; i < queue.length; i++) {
        queue[i].call(this, e);
    }
}

function Control() {
    this.domEvents = {};
}

Control.prototype.addDOMEvent = function (element, type, handler) {
    var id = element['__ui_event_identity__'];
    if (!id) {
        element['__ui_event_identity__'] = helper.getGUID();
    }

    if (!domEvents[id]) {
        domEvents[id] = { element: element }; // 没有事件名字叫element的
    }
    var events = domEvents[id];

    var queue = events[type];
    if (!queue) {
        queue = events[type] = [];
        var trigger = lib.bind(triggerDOMEvent, this);
        queue.handler = trigger;
        element.addEventListener(type, trigger, false);
    }

    queue.push(handler);
};

Control.prototype.clearDOMEvents = function () {
    for (var id in this.domEvents) {
        // 没加hasOwnProperty
        var events = this.domEvents[id].element;
        var element = events.element;
        delete events.element; // 防内存泄露外加让下面的for正常工作
        for (var type in events) {
            // 没加hasOwnProperty
            element.removeEventListener(type, events[type].handler, false);
        }
    }

    // 所有东西丢光
    this.domEvents = {};
};

@otakustay
Copy link
Member Author

上面这个方案的很大一个缺陷是,在控件还活着的时候如果有移除子元素,内存回收不掉(一直在domEvents中被存着),除非我们的clearDOMEvents接受一个HTMLElement作为可选参数,单独移掉这一个元素相关的事件。

@errorrik
Copy link

上面这个方案,看起来在控件dispose时还是能移除掉的。只不过重复render的过程如果不断重新构建子元素则老的移除不掉。

还有,这货貌似应该弄helper里

所以为什么我有时候喜欢生成onclick=“”,因为不用处理释放

@otakustay
Copy link
Member Author

还有,这货貌似应该弄helper里

放helper里是必须的,昨天那时间脑子没这么好使一急就随便写在Control上了。

如果OK的话,我以这个方案实现一下吧?另外会提供可以针对单个HTMLElement移除事件的方法,控件开发者注意调用

@errorrik
Copy link

你在数组上挂handler属性,真狠。。。

我觉得没问题哈

@otakustay
Copy link
Member Author

已经在 b1756fb 中加上了,还没测试,测试稍后补上

@otakustay
Copy link
Member Author

@errorrik 有空把基类中的一些事件迁移到这上面吧

@otakustay
Copy link
Member Author

测试在 e1b0324 补上了,不全面但能保证基本的正确性,未来有遇上问题再补相应测试用例。

另外各位如果有遇到比较纠结怕出错的地方,或者要改某一处的代码,建议先把测试补了

3610cn added a commit that referenced this issue Jul 20, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants