`

Ext2.02事件机制缺陷分析,以及解决方案 ( 3-20更新 )

阅读更多
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;

            },



分享到:
评论
21 楼 qintao1203 2008-09-05  
呵呵!
按照fins说的: 我们只能努力来避免孤立节点的生成
把js代码重写了下,问题解决了!!
感谢fins,通过你的帖子才找到出问题的原因!
20 楼 qintao1203 2008-09-05  
找到你说的几个ext js的地方了,现在不知道把改的js怎么放到,ext-all和ext-base里去!!
19 楼 fins 2008-09-05  
孤立节点我们无法通过编程手段来删除
我们只能努力来避免孤立节点的生成
18 楼 qintao1203 2008-09-05  
不会改源码!
有没有就在自己写的js里来销毁那些孤立节点的!!
17 楼 nihongye 2008-03-20  
随手写的 没有测试过的喔,因为我那样改动后,不再需要做fn._handlers的remove工作了,所以在 destropy el的时候不需要调用这里的stopListener,因为listeners被el引用,所以去掉对el的引用,也就相应的去掉listeners的引用,再没有别的地方需要去掉(调用Ext.lib.Event是必须的)。对 stopListener的改动是为了保持原来stopListener的正确性。
----------------------------------------------------------------------------------------------------------------------------------
做了测试,按我的写法,泄漏的不行。。。
16 楼 fins 2008-03-20  
nihongye 写道
怎么说呢,fn去记录_handlers,导致不容易清除_handlers,直接的想法就是让el来做这事情。觉得Ext.lib.Event的事它自己管,EventManager的事自己管,干净。
引用

提到的Pseudo-Leaks对于一页式的ajax应用简直就是噩梦。。。


这个fn 不是只属于一个el的 更不是el自己的


我来详细阐述一下我的观点:


一个事件里的几个核心角色:

元素(el) 事件名(eventName) 原始函数(fn) 被包装函数(h) 二次包装函数(wfn)


这些东西的地位是一样重要的 存放在一起应该更合适.
所以我还是觉得对 现有的  listeners 做扩展 让他多装一个元素比较合适.



事实上,我觉得ext的 fn._handlers 的做法也并不好
首先他改变了原始函数, 这个做法实际上很欠妥当
另外一个也是违背了我上面提到的统一管理的原则.

他这么做无非是为了达到下面的几个目的:
1 可以根据一个fn得到包装后的 h
2 可以知道一个fn 被注册到了哪些元素上
3 可以知道一个fn 都被哪些事件类型调用


如果他能够像我刚刚的做法 ,对 listeners 做一个扩充, 把 fn也加进去
那么根本就不需要这个蹩脚的  fn._handlers 了

你觉得 这个 fn._handlers 是一个好的设计吗?


如果你也觉得这个设计不好  那么 去掉fn._handlers 后,按照你的设计 el上缓存 fn
我怎么能够达到 上面提到的三个目的呢?


综上所述 我还是推崇 "统一存放 统一管理"

为了更好的对整个事件层进行统一的管理和控制,将事件各个核心角色统一缓存比较好,
而不要每个元素自己来缓存自己的事件信息.


关于设计的问题 一向很难统一, 一个软件可能有多种设计是合理的 也可能任何设计都不是很合理的.
所以关于设计的问题就不争论了. 


===============================
我更关心的是结果  我想问一下 你的代码你测试过吗? 确实有效吗

因为我前面提过 在 destroy时 根本不会调用 stopListening方法
你对stopListening做的修改不会起到作用吧???



 




15 楼 nihongye 2008-03-20  
怎么说呢,fn去记录_handlers,导致不容易清除_handlers,直接的想法就是让el来做这事情。觉得Ext.lib.Event的事它自己管,EventManager的事自己管,干净。
引用

提到的Pseudo-Leaks对于一页式的ajax应用简直就是噩梦。。。
14 楼 fins 2008-03-20  
不建议在 el上做 缓存

理由很简单

从ext整体的设计来看 他还是推崇 将所有的 listener 放到一处管理
也就是 Ext.lib.Event里的那个 私有的 listeners

从设计角度来讲 这样统一管理也是好的

如果每个 element单独记录自己的事件  不便于实现一套 "框架级的统一事件管理机制"

所以我觉得在el上做文章不好

13 楼 nihongye 2008-03-20  
EventManager.js的140行开始:
       //将原来的fn_handlers去掉,增加
        el._listeners = el._listeners || [];

        el._listeners.push([ename, fn,h]);



        E.on(el, ename, h);

        if(ename == "mousewheel" && el.addEventListener){ // workaround for jQuery

            el.addEventListener("DOMMouseScroll", h, false);

            E.on(window, 'unload', function(){

                el.removeEventListener("DOMMouseScroll", h, false);

            });

        }

        if(ename == "mousedown" && el == document){ // fix stopped mousedowns on the document

            Ext.EventManager.stoppedMouseDownEvent.addListener(h);

        }

        return h;

    };



    var stopListening = function(el, ename, fn){

        var hds = el._listeners, hd = fn;

        if(hds){

            for(var i = 0, len = hds.length; i < len; i++){

                var h = hds[i];

                if(h[0] == ename && h[1] == fn){//无需Id,增加h[1]==fn的判断

                    hd = h[2];

                    hds.splice(i, 1);

                    break;

                }

            }

        }

        E.un(el, ename, hd);

假设没有全局引用el,就不会有listener泄漏之忧了吧。所以Element.js的removeAllListeners也不用改了,因为引用listen的是el本身。是否el是dom节点的话,那么ie就内存泄漏了?
12 楼 fins 2008-03-20  
2008-03-20更新一个临时解决方案

见主楼
11 楼 hax 2008-03-20  
根据roadmap还起码要忍9个月。。。奥运都结束了。。。
10 楼 sp42 2008-03-20  
EXT的Observer架构源自YUI.
不过无论社区还是开发团队,已经对这个架构颇有微辞。

jack他们安排在3.0会对这项有改进:
引用
Update the Ext event registration model

http://extjs.com/roadmap
9 楼 fins 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的事件机制肯定可以化简的






8 楼 fins 2008-03-19  
我写的是伪代码
你仔细跟踪一下 肯定是一样的
7 楼 hax 2008-03-19  
我看到2.0.2跟fins贴出来的代码有些不同啊。
6 楼 hax 2008-03-19  
我没用过ext,刚刚看了一下,它是有若干种adapter,可以用standalone或者用yui,jquery,prototype。evt机制似乎也是委托给这些不同的底层包的。这个bug是否是只是standalone才有呢?
5 楼 nihongye 2008-03-19  
绑定的时候el.lisenters.push([eventName,fn])。
然后removeAllLis...的就逐一调用stop。

4 楼 hax 2008-03-19  
ext的event机制似乎原先是基于YUI的?
3 楼 hax 2008-03-19  
btw,我下了ext2.0.2的源代码来看,其中空格和tab混用,缩进有点乱,看的我很不爽。
2 楼 差沙 2008-03-19  
这一问题的根本原因还是,ext要兼容其他lib的问题。
其实ext3.0的时候应该把其他的Lib统统扔掉了,不需要什么adapter,这样底层的东西就能互通了。

相关推荐

    ext2.02文档下载

    "mimetype"和"ext_20docs.xml"通常是电子书或离线文档的标准组成部分,"mimetype"指定了文档的类型,而"ext_20docs.xml"可能是EXT2.02 API文档的元数据或索引文件。 总的来说,EXT2.02文档下载提供了EXT框架的重要...

    Ext2.02的一些总结

    但我们可以从博客链接(https://pengjj2.iteye.com/blog/1197832)推测,博主可能分享了他们在使用Ext JS 2.02版本时的经验、遇到的问题及解决方案,或者对新特性和性能优化进行了分析。 标签“源码”和“工具”...

    ext 2.02 sample包

    ext 2.02 sample包ext 2.02 sample包ext 2.02 sample包ext 2.02 sample包ext 2.02 sample包

    EXT2.02_API

    EXT2.02_API 对方法和属性都有详细的介绍

    grub-2.02-beta2-for-windows.zip

    这个zip文件中包含的“grub-2.02~beta2-for-windows”可能是GRUB2的Windows安装程序或者工具集,用于在Windows下安装、更新或修复GRUB2配置。使用这样的工具,用户无需在Linux环境中进行操作,简化了对GRUB2的管理和...

    Ext Js2.02 api

    在Ext Js 2.02中,表单组件(FormPanel)提供了一套完整的表单创建和验证机制。开发者可以方便地添加各种输入控件,如文本框、下拉列表、日期选择器等,并通过Validator进行数据验证,确保用户输入的数据符合业务...

    ext-2.0 ext-2.0 ext-2.0 ext-2.0 ext-2.0

    ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0ext-2.0

    ext-d3-pivot-d3-component.zip

    Pivot-D3是EXT-D3的一个补充,它提供了一种高效的数据透视表解决方案。数据透视表是一种强大的数据分析工具,允许用户快速汇总、分析和理解大量数据。Pivot-D3组件通过EXT JS的API提供这种功能,用户可以通过拖放...

    spketdwcs-ext-2.1.mxp

    spketdwcs-ext-2.1.mxpspketdwcs-ext-2.1.mxpspketdwcs-ext-2.1.mxpspketdwcs-ext-2.1.mxpspketdwcs-ext-2.1.mxpspketdwcs-ext-2.1.mxpspketdwcs-ext-2.1.mxpspketdwcs-ext-2.1.mxpspketdwcs-ext-2.1.mxpspketdwcs-...

    Ext入门-详细教程

    Ext入门-详细教程Ext入门-详细教程Ext入门-详细教程Ext入门-详细教程Ext入门-详细教程Ext入门-详细教程Ext入门-详细教程Ext入门-详细教程Ext入门-详细教程Ext入门-详细教程Ext入门-详细教程Ext入门-详细教程Ext入门-...

    ext-ms-win-gdi-desktop-l1-1-0.dll

    ext-ms-win-gdi-desktop-l1-1-0.dll 用于解决这个dll文件丢失问题,下载后将此文件放置在相关文件根目录下,即可解决丢失问题

    ext3.jar ext使用非常多

    在实际项目中,开发者可以通过Maven或者Gradle等构建工具将EXT3.jar作为依赖引入,然后在Java代码中创建EXT组件并进行事件绑定,实现业务逻辑。 在网站开发中,EXT框架的使用可以提供强大的用户界面,例如,通过EXT...

    Extjs源码之--Ext事件机制/继承关系

    在“Extjs源码之--Ext事件机制/继承关系”中,我们将深入探讨EXTJS如何处理事件以及其类层次结构。EXTJS的事件处理主要由EventManager.js这个文件负责,它是EXTJS事件系统的基础。 首先,EXTJS的事件机制基于观察者...

    Ext-JS-4.1-Beta-3.zip

    3. **ext-all-dev.js, ext-all-debug-w-comments.js, ext-all-debug.js, ext-all.js, ext-debug.js, ext.js**:这些都是Ext JS的核心库文件,不同文件的用途各有侧重: - `ext-all-dev.js`:包含了所有组件和功能,...

    chinese-bert-wwm-ext.rar

    《哈工大版Chinese-BERT-wwm-ext for PyTorch深度解析》 在自然语言处理(NLP)领域,预训练模型已经成为基石,而BERT(Bidirectional Encoder Representations from Transformers)模型更是其中的明星。本文将深入...

    DW CS3--EXT2.2插件

    DW CS3--EXT2.2插件DW CS3--EXT2.2插件DW CS3--EXT2.2插件DW CS3--EXT2.2插件DW CS3--EXT2.2插件

    ext-3-to-4-migration-pack.zip

    这个"ext-3-to-4-migration-pack.zip"包含的兼容性文件可能用于解决不同硬件或旧版软件与EXT4之间的兼容性问题,确保升级过程顺利进行。教程将详细指导用户如何执行上述步骤,以及如何处理可能出现的问题。通过遵循...

    Ext JS源码分析与开发实例宝典光盘源码

    Ext JS源码分析与开发实例宝典光盘源码Ext JS源码分析与开发实例宝典光盘源码Ext JS源码分析与开发实例宝典光盘源码Ext JS源码分析与开发实例宝典光盘源码Ext JS源码分析与开发实例宝典光盘源码Ext JS源码分析与开发...

    EXT 未指明错误---解决方案备份

    3. **错误日志分析**:如果EXT JS在运行时抛出错误,通常会在控制台打印出错误信息。这些信息对于定位问题非常有帮助,因为它们会显示错误的类型、发生位置和可能的原因。 4. **版本兼容性**:确认EXT JS库的版本与...

    jcifs-ext-0.9.4.jar

    总结来说,jcifs-ext-0.9.4.jar是Java应用与AD域集成的重要工具,特别是对于基于CAS的SSO解决方案。它简化了身份验证过程,增强了跨平台的兼容性和安全性。在设计和实现涉及Windows域服务的Java应用时,jcifs-ext库...

Global site tag (gtag.js) - Google Analytics