Ext2.02事件机制缺陷分析,以及解决方案 (fins)
网址:
http://fins.javaeye.com/blog/173818
2008-03-20更新一个临时解决方案.
测试发现,Ext2.02在IE下无法正常释放被删除的元素(当该元素被注册了事件时)
经过分析 发现ext事件机制中的一个bug
(
bug 具体描述见: http://fins.javaeye.com/blog/173218
测试使用工具见: http://fins.javaeye.com/blog/172891
)
使用 el.on(eventName, fn) 为el添加事件
调用 Ext.destroy(el) 方法移除el
此时,如果fn为全局类型,或者是被全局对象引用, 那么会使el元素成为孤立节点,无法彻底移除.
而如果在 Ext.destroy(el) 之前, 调用 el.un(eventName, fn) 移除添加的事件,
那么就可以彻底移除. 但是直接使用 Ext.destroy 才是ext中描述的正确做法,
切ext内部也都是这样使用的, 所以应该将解决问题的着手点放在 el.on 和 Ext.destroy方法上.
经过测试 Ext.destroy el.removeNode 均无问题. 核心问题在 事件机制. 下面详细分析一下.
==============================
产生问题的原因
执行Element.removeAllListeners时没有调用 EventManager.stopListener中的
"删除 fn._handlers 缓存内的相关数据 "
导致在IE下 当 fn 为全局对象 或者是被引用时, 元素无法被正确移除.
-----------------------------------------
如果只是简单的修改 Element.removeAllListeners
让其 调用 Ext.Event.un 时 改成调用 EventManager.stopListener 是不行的
因为 Element.removeAllListeners 调用 Ext.Event.un 时 ,传递的函数参数是h, 而不是最初的fn
但是 EventManager.stopListener需要得到 最初的fn.
-----------------------------------------
现在的情况是 从 fn 能找到h (fn._handlers) ,但是 通过h无法找到fn
缓存Ext.Event._listeners 中也没有存放 最初的fn.
-----------------------------------------
也许可以考虑在 removeAllListeners 或 purgeElement 中对 fn._handlers 进行清除,但是 拿不到 最初的fn
-----------------------------------------
如果之前 强制 做一个引用, 例如 h._core =fn;
然后在 Element.removeAllListeners 加以利用 利用完之后 再清除, 似乎看起来不错
但是我试了 ,失败 !!!!
具体原因我也说不清
==================================
我觉得 如果要解决 这个bug 确实要对ext的整个事件机制做一番大改动.(恕我直言,ext的这套事件机制真的有点太.... )
以上是我最近研究的成果
发上来和大家分享,如果说的不对 请务必一定马上纠正我, 以免误人子弟 谢谢大家了
======================================
下面附上刚刚写出的解决方案,请大家拍砖, 我想肯定还有更好的方法.
第一步 ========================
EventManager.js 153行
第二步 ========================
ext-base.js 227行
http://fins.javaeye.com/blog/173818
2008-03-20更新一个临时解决方案.
测试发现,Ext2.02在IE下无法正常释放被删除的元素(当该元素被注册了事件时)
经过分析 发现ext事件机制中的一个bug
(
bug 具体描述见: http://fins.javaeye.com/blog/173218
测试使用工具见: http://fins.javaeye.com/blog/172891
)
使用 el.on(eventName, fn) 为el添加事件
调用 Ext.destroy(el) 方法移除el
此时,如果fn为全局类型,或者是被全局对象引用, 那么会使el元素成为孤立节点,无法彻底移除.
而如果在 Ext.destroy(el) 之前, 调用 el.un(eventName, fn) 移除添加的事件,
那么就可以彻底移除. 但是直接使用 Ext.destroy 才是ext中描述的正确做法,
切ext内部也都是这样使用的, 所以应该将解决问题的着手点放在 el.on 和 Ext.destroy方法上.
- =============================
- 销毁元素的方法(很简单)
- =============================
- Ext.destroy(el){
- el.removeAllListeners();
- el.removeNode();
- }
经过测试 Ext.destroy el.removeNode 均无问题. 核心问题在 事件机制. 下面详细分析一下.
- =============================
- 给一个元素添加事件
- =============================
- Element.on(eventName, fn) {
- el=this;
- 调用 EventManager.on( el,eventName, fn ){
- 调用 EventManager.listener( el,eventName, fn ){
- 包装 h <---- fn
- 缓存 fn._handlers <---- [ [h] ]
- 调用 Ext.Event.on( el,eventName, h ) {
- 包装 wfn <---- h
- 缓存 Ext.Event._listeners <---- [ el , eventName, h, wfn ]
- el.addEvent( wfn )
- }
- }
- }
- }
- 注意:真正注册到el上的事件是wfn
- =============================
- 移除一个元素的事件
- =============================
- Element.un(eventName, fn) {
- el=this;
- 调用 EventManager.un( el,eventName, fn ){
- 调用 EventManager.stopListener( el,eventName, fn ){
- 取得之前缓存的 h <---- fn._handlers
- 删除 fn._handlers 缓存内的相关数据
- 调用 Ext.Event.un( el,eventName, h ) {
- 取得之前缓存的 wfn <---- Ext.Event._listeners
- el.removeEvent( wfn )
- 删除 Ext.Event._listeners 缓存内的相关数据
- }
- }
- }
- }
- =============================
- 移除一个元素的所有注册的事件
- =============================
- Element.removeAllListeners() {
- el=this;
- 调用 Ext.Event.purgeElement(el){
- 取得缓存中所有的和el相关的信息 l[] <---- Ext.Event._listeners
- <循环开始 l[] >
- 从 l中取得 eventName <---- l[i];
- 从 l中取得 h <---- l[i];
- 调用 Ext.Event.un( el,eventName, h ) {
- 取得之前缓存的 wfn <---- Ext.Event._listeners
- el.remove( wfn )
- 删除 Ext.Event._listeners 缓存内的相关数据
- }
- <循环结束>
- }
- }
==============================
产生问题的原因
执行Element.removeAllListeners时没有调用 EventManager.stopListener中的
"删除 fn._handlers 缓存内的相关数据 "
导致在IE下 当 fn 为全局对象 或者是被引用时, 元素无法被正确移除.
-----------------------------------------
如果只是简单的修改 Element.removeAllListeners
让其 调用 Ext.Event.un 时 改成调用 EventManager.stopListener 是不行的
因为 Element.removeAllListeners 调用 Ext.Event.un 时 ,传递的函数参数是h, 而不是最初的fn
但是 EventManager.stopListener需要得到 最初的fn.
-----------------------------------------
现在的情况是 从 fn 能找到h (fn._handlers) ,但是 通过h无法找到fn
缓存Ext.Event._listeners 中也没有存放 最初的fn.
-----------------------------------------
也许可以考虑在 removeAllListeners 或 purgeElement 中对 fn._handlers 进行清除,但是 拿不到 最初的fn
-----------------------------------------
如果之前 强制 做一个引用, 例如 h._core =fn;
然后在 Element.removeAllListeners 加以利用 利用完之后 再清除, 似乎看起来不错
但是我试了 ,失败 !!!!
具体原因我也说不清
==================================
我觉得 如果要解决 这个bug 确实要对ext的整个事件机制做一番大改动.(恕我直言,ext的这套事件机制真的有点太.... )
以上是我最近研究的成果
发上来和大家分享,如果说的不对 请务必一定马上纠正我, 以免误人子弟 谢谢大家了
======================================
下面附上刚刚写出的解决方案,请大家拍砖, 我想肯定还有更好的方法.
第一步 ========================
EventManager.js 153行
- //修改 Ext.EventManager的 私有方法 listen
- // E.on(el, ename, h);
- // 改为如下 (即,多传一个最初的 fn)
- E.on(el, ename, h , fn);
第二步 ========================
ext-base.js 227行
- //修改 Ext.lib.Event 的 addListener 和 removeListener 方法
- addListener: function(el, eventName, fn , ofn) {
- el = Ext.getDom(el);
- if (!el || !fn) {
- return false;
- }
- if ("unload" == eventName) {
- unloadListeners[unloadListeners.length] =
- [el, eventName, fn];
- return true;
- }
- // prevent unload errors with simple check
- var wrappedFn = function(e) {
- return typeof Ext != 'undefined' ? fn(Ext.lib.Event.getEvent(e)) : false;
- };
- var li = [el, eventName, fn, wrappedFn,ofn];
- var index = listeners.length;
- listeners[index] = li;
- this.doAdd(el, eventName, wrappedFn, false);
- return true;
- },
- removeListener: function(el, eventName, fn) {
- var i, len;
- el = Ext.getDom(el);
- if(!fn) {
- return this.purgeElement(el, false, eventName);
- }
- if ("unload" == eventName) {
- for (i = 0,len = unloadListeners.length; i < len; i++) {
- var li = unloadListeners[i];
- if (li &&
- li[0] == el &&
- li[1] == eventName &&
- li[2] == fn) {
- unloadListeners.splice(i, 1);
- return true;
- }
- }
- return false;
- }
- var cacheItem = null;
- var index = arguments[3];
- if ("undefined" == typeof index) {
- index = this._getCacheIndex(el, eventName, fn);
- }
- if (index >= 0) {
- cacheItem = listeners[index];
- }
- if (!el || !cacheItem) {
- return false;
- }
- this.doRemove(el, eventName, cacheItem[this.WFN], false);
- fn=listeners[index][4];
- if (fn){
- var id = Ext.id(el), hds = fn._handlers, hd = fn;
- if(hds){
- for(var i = 0, len = hds.length; i < len; i++){
- var h = hds[i];
- if(h[0] == id && h[1] == eventName){
- hd = h[2];
- hds.splice(i, 1);
- break;
- }
- }
- }
- }
- delete listeners[index][this.WFN];
- delete listeners[index][this.FN];
- listeners.splice(index, 1);
- return true;
- },