精华帖 (0) :: 良好帖 (7) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2008-03-19
2008-03-20更新一个临时解决方案.
测试发现,Ext2.02在IE下无法正常释放被删除的元素(当该元素被注册了事件时) 经过分析 发现ext事件机制中的一个bug ( bug 具体描述见: http://fins.iteye.com/blog/173218 测试使用工具见: http://fins.iteye.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; }, 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2008-03-19
复杂到有点头大。感觉ext的这套东西是api演化过程中的产物,将来其实现可能会重构。
|
|
返回顶楼 | |
发表时间:2008-03-19
这一问题的根本原因还是,ext要兼容其他lib的问题。
其实ext3.0的时候应该把其他的Lib统统扔掉了,不需要什么adapter,这样底层的东西就能互通了。 |
|
返回顶楼 | |
发表时间:2008-03-19
btw,我下了ext2.0.2的源代码来看,其中空格和tab混用,缩进有点乱,看的我很不爽。
|
|
返回顶楼 | |
发表时间:2008-03-19
ext的event机制似乎原先是基于YUI的?
|
|
返回顶楼 | |
发表时间:2008-03-19
绑定的时候el.lisenters.push([eventName,fn])。
然后removeAllLis...的就逐一调用stop。 |
|
返回顶楼 | |
发表时间:2008-03-19
我没用过ext,刚刚看了一下,它是有若干种adapter,可以用standalone或者用yui,jquery,prototype。evt机制似乎也是委托给这些不同的底层包的。这个bug是否是只是standalone才有呢?
|
|
返回顶楼 | |
发表时间:2008-03-19
我看到2.0.2跟fins贴出来的代码有些不同啊。
|
|
返回顶楼 | |
发表时间:2008-03-19
我写的是伪代码
你仔细跟踪一下 肯定是一样的 |
|
返回顶楼 | |
发表时间:2008-03-19
nihongye 写道 绑定的时候el.lisenters.push([eventName,fn])。
然后removeAllLis...的就逐一调用stop。 这个试过了 不行的 事实上你仔细看一下你就会发现 不管是 listeners 还是 handlers 那么都没有挂在 el上 我想jack应该是考虑到一些潜藏的隐患了 例如循环引用 h里调fn fn里保留 h wfn 里调h listeners 里保留 el eventname h wfn 如果 el 上 再保留一份 fn ... ... 想想就头大 我觉得ext的事件机制肯定可以化简的 |
|
返回顶楼 | |