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方法上.


Javascript代码 复制代码
  1. =============================   
  2. 销毁元素的方法(很简单)   
  3. =============================   
  4. Ext.destroy(el){   
  5.     el.removeAllListeners();   
  6.     el.removeNode();   
  7. }  
=============================
销毁元素的方法(很简单)
=============================
Ext.destroy(el){
	el.removeAllListeners();
	el.removeNode();
}

经过测试 Ext.destroy el.removeNode 均无问题. 核心问题在 事件机制. 下面详细分析一下.


Javascript代码 复制代码
  1. =============================   
  2. 给一个元素添加事件   
  3. =============================   
  4. Element.on(eventName, fn) {   
  5.     el=this;   
  6.     调用 EventManager.on( el,eventName, fn ){   
  7.         调用 EventManager.listener( el,eventName, fn ){   
  8.             包装   h <---- fn   
  9.             缓存  fn._handlers <---- [ [h]  ]   
  10.             调用 Ext.Event.on( el,eventName, h )  {   
  11.                 包装   wfn <---- h   
  12.                 缓存  Ext.Event._listeners  <---- [ el , eventName, h, wfn ]   
  13.                 el.addEvent( wfn )    
  14.             }   
  15.         }   
  16.     }   
  17. }   
  18. 注意:真正注册到el上的事件是wfn  
=============================
给一个元素添加事件
=============================
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


Javascript代码 复制代码
  1. =============================   
  2. 移除一个元素的事件   
  3. =============================   
  4. Element.un(eventName, fn) {   
  5.     el=this;   
  6.     调用 EventManager.un( el,eventName, fn ){   
  7.         调用 EventManager.stopListener( el,eventName, fn ){   
  8.             取得之前缓存的 h <---- fn._handlers    
  9.             删除 fn._handlers  缓存内的相关数据    
  10.             调用 Ext.Event.un( el,eventName, h )  {   
  11.                 取得之前缓存的  wfn  <---- Ext.Event._listeners   
  12.                 el.removeEvent( wfn )    
  13.                 删除 Ext.Event._listeners 缓存内的相关数据    
  14.             }   
  15.   
  16.         }   
  17.     }   
  18. }  
=============================
移除一个元素的事件
=============================
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 缓存内的相关数据 
			}

		}
	}
}


Javascript代码 复制代码
  1. =============================   
  2. 移除一个元素的所有注册的事件   
  3. =============================   
  4. Element.removeAllListeners() {   
  5.     el=this;   
  6.     调用 Ext.Event.purgeElement(el){   
  7.         取得缓存中所有的和el相关的信息   l[] <---- Ext.Event._listeners   
  8.         <循环开始 l[] >   
  9.             从 l中取得  eventName <---- l[i];   
  10.             从 l中取得  h <---- l[i];   
  11.             调用 Ext.Event.un( el,eventName, h )  {   
  12.                 取得之前缓存的  wfn  <---- Ext.Event._listeners   
  13.                 el.remove( wfn )    
  14.                 删除 Ext.Event._listeners 缓存内的相关数据    
  15.             }   
  16.         <循环结束>   
  17.   
  18.     }   
  19.   
  20. }  
=============================
移除一个元素的所有注册的事件
=============================
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行


Javascript代码 复制代码
  1. //修改 Ext.EventManager的 私有方法 listen   
  2. //   E.on(el, ename, h);   
  3. // 改为如下 (即,多传一个最初的 fn)   
  4.   
  5.     E.on(el, ename, h , fn);  
//修改 Ext.EventManager的 私有方法 listen
//	 E.on(el, ename, h);
// 改为如下 (即,多传一个最初的 fn)

	E.on(el, ename, h , fn);



第二步 ========================

ext-base.js 227行

Javascript代码 复制代码
  1. //修改 Ext.lib.Event 的  addListener 和 removeListener 方法   
  2.   
  3. addListener: function(el, eventName, fn , ofn) {   
  4.   
  5.                 el = Ext.getDom(el);   
  6.   
  7.                 if (!el || !fn) {   
  8.                     return false;   
  9.                 }   
  10.   
  11.                 if ("unload" == eventName) {   
  12.                     unloadListeners[unloadListeners.length] =   
  13.                     [el, eventName, fn];   
  14.                     return true;   
  15.                 }   
  16.   
  17.                 // prevent unload errors with simple check   
  18.                 var wrappedFn = function(e) {   
  19.                     return typeof Ext != 'undefined' ? fn(Ext.lib.Event.getEvent(e)) : false;   
  20.                 };   
  21.        
  22.                 var li = [el, eventName, fn, wrappedFn,ofn];   
  23.                 var index = listeners.length;   
  24.                 listeners[index] = li;   
  25.   
  26.                 this.doAdd(el, eventName, wrappedFn, false);   
  27.                 return true;   
  28.   
  29.             },   
  30.   
  31.             removeListener: function(el, eventName, fn) {   
  32.                 var i, len;   
  33.   
  34.                 el = Ext.getDom(el);   
  35.   
  36.                 if(!fn) {   
  37.                     return this.purgeElement(el, false, eventName);   
  38.                 }   
  39.   
  40.   
  41.                 if ("unload" == eventName) {   
  42.   
  43.                     for (i = 0,len = unloadListeners.length; i < len; i++) {   
  44.                         var li = unloadListeners[i];   
  45.                         if (li &&   
  46.                             li[0] == el &&   
  47.                             li[1] == eventName &&   
  48.                             li[2] == fn) {   
  49.                             unloadListeners.splice(i, 1);   
  50.                             return true;   
  51.                         }   
  52.                     }   
  53.   
  54.                     return false;   
  55.                 }   
  56.   
  57.                 var cacheItem = null;   
  58.   
  59.   
  60.                 var index = arguments[3];   
  61.   
  62.                 if ("undefined" == typeof index) {   
  63.                     index = this._getCacheIndex(el, eventName, fn);   
  64.                 }   
  65.   
  66.                 if (index >= 0) {   
  67.                     cacheItem = listeners[index];   
  68.                 }   
  69.   
  70.                 if (!el || !cacheItem) {   
  71.                     return false;   
  72.                 }   
  73.   
  74.         this.doRemove(el, eventName, cacheItem[this.WFN], false);   
  75.         fn=listeners[index][4];   
  76.         if (fn){   
  77.             var id = Ext.id(el), hds = fn._handlers, hd = fn;   
  78.             if(hds){   
  79.                 for(var i = 0, len = hds.length; i < len; i++){   
  80.                     var h = hds[i];   
  81.                     if(h[0] == id && h[1] == eventName){   
  82.                         hd = h[2];   
  83.                         hds.splice(i, 1);   
  84.                         break;   
  85.                     }   
  86.                 }   
  87.             }   
  88.         }   
  89.         delete listeners[index][this.WFN];   
  90.                 delete listeners[index][this.FN];   
  91.                 listeners.splice(index, 1);   
  92.   
  93.                 return true;   
  94.   
  95.             },  
//修改 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;

            },

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值