`
clue
  • 浏览: 30430 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

Ext&OPOA的内存释放改进 (Ext3.0.0)

    博客分类:
  • web
阅读更多
帖子发了好几天,可惜么人回 
好在经过几天的努力,发现这个问题也没那么困难。
目前修改了一些Ext组件,使用sIEve检查发现除form节点外,其它的都可以正常释放掉。
(form节点无法释放好像是IE自身的特性)
不过只是想要个是否可行的答案罢了,现在看来貌似是可行的。

自己做的补丁帖在3楼
由于是做非常简单的可行性测试,所以只有部分标准UI组件的修正,有兴趣的可以回帖交流。

---2009-9-14

-----------------------------传说中的分割线---------------------------------------

最近准备使用Ext3.0.0开发一个one page one application的网站
预先做了一个测试页面,使用动态加载JS,多Tab切换,无iframe。

界面效率问题不用说了,一直就那样,还勉强可以接受。
但在IE下测试内存占用的时候,发现问题真的很严重。
一个新Tab页面,里面有两Tab,每Tab里有Tree、Grid、Chart、Form面板各一个
每多开一个Tab页,内存占用上升9M左右。关闭Tab页释放的内存也极少
开始以为IE本身特性就是这样(不立即释放内存)
但测试开了20来个Tab页,性能下降了一倍左右,内存占用近200M,关闭Tab页也没有释放几M。

于是使用sIEve来检测,发现每开关一个Tab页,都会出现近100来个孤立(Orphan)Dom节点无法释放。
在开关20来次后,总Dom节点达到了近5K的水平
Dom节点无法释放,肯定是有引用它们的JS对象,这些对象也属于无用数据,应当被清除掉才对。

后来上网查找资料,发现Ext2.2有人写过一个补丁,也是做opoa时发现问题并手工修正的:
http://www.extjs.com/forum/showthread.php?t=45782
这个帖的作者是中国人: Location: nanning, china
不过我们要用到Ext3.0的一些新功能,所以不会考虑Ext2.2
并且他好像很久没上过线,也没有其它联系方式可以交流,真的很可惜。

实在查不到可用的解决方案只好自己尝试:
查找各组件的destroy方法,看它是否完全销毁了各种引用
换句话说,看它是否将自己创建的已经无用的对象销毁,是否将自身与其它任何dom节点、组件切断关系。
没有的话就给它打补丁。

接触Ext也没多久,忙了好几天,只修改了几个组件,并且还不知道有没有其它问题。

例如Ext.Panel组件的修改:
Ext.override(Ext.Panel,{
    onDestroy : function(){
        Ext.destroy(
            this.header,
            this.tbar,
            this.bbar,
            this.footer,
            this.body,
            this.bwrap,
			this.dd
        );
		delete this.header;
		delete this.tbar;
		delete this.bbar;
		delete this.footer;
		delete this.body;
		delete this.bwrap;
		delete this.dd;
        Ext.Panel.superclass.onDestroy.call(this);
    }
});

注:原beforeDestroy没有考虑this.dd的销毁,没有彻底清除dom节点,没有最后delete解引用
开始是直接修改beforeDestroy方法的,但发现删除顺序有些问题,造成继承它的一些组件destroy时出错
所以新建了onDestroy方法
这些属性都是在Panel这一层定义的,销毁也应该在这一层进行。

以上示例测试过,发现所有节点都能释放。

另外还尝试过修改Ext.dd、Ext.ux.Portal的释放
由于水平不足,对自己的修改很没信心,就先不帖出来了。

希望这帖子能起到抛砖引玉的作用,大家一起讨论解决方法
(有点私心的说 )
分享到:
评论
7 楼 swbssd 2010-04-30  
楼主我用你的方法释放panel,使用sIEve查看 好象没什么效果啊,内存还是没释放..
6 楼 atian25 2009-09-16  
不错的帖子.

3.0.1的官方说明改进了不少,不过搞不到补丁包...


btw,楼主的帖子的分类错了,不在EXT下.
论坛有bug,在blog那边修改后,论坛这边的分类会出问题.
5 楼 Sean220 2009-09-16  
不错的主题,这是EXT做OAOP的致命伤,好像官方一直没有很好地解决这个问题
4 楼 witcheryne 2009-09-15  
我用的是Ext Gwt[即:Gxt] ...  也碰见效率问题...
    我的解决方法是,尽量使用单例模式... 然后自己写清空代码...
这样做后,第一次加载比较慢, 之后会非常快..

关于内存释放问题一直没研究过,有空研究研究...
3 楼 clue 2009-09-14  
中间还改了一些使用中发现的BUG,另外还参考了Ext3.0.1的部分修正。
(Ext3.0.1是从某个使用正版的网站上偷下来的……)


ext-patch.js
// ******************************* Bugs fix ***********************************

Ext.override(Ext.chart.Chart, {
	onDestroy: function(){
		this.bindStore(null);
		var tip = this.tipFnName;
		if(!Ext.isEmpty(tip)){
			delete window[tip];
		}
		Ext.chart.Chart.superclass.onDestroy.call(this);
	}
});

Ext.override(Ext.form.TextField,{
	autoSize : function(){
        if(!this.grow || !this.rendered){
            return;
        }
        if(!this.metrics){
            this.metrics = Ext.util.TextMetrics.createInstance(this.el);
        }
        var el = this.el;
        var v = el.dom.value;
        var d = document.createElement('div');
        d.appendChild(document.createTextNode(v));
        v = d.innerHTML;
        Ext.removeNode(d);
		d = null;
        v += ' ';
        var w = Math.min(this.growMax, Math.max(this.metrics.getWidth(v) + /* add extra padding */ 10, this.growMin));
        this.el.setWidth(w);
        this.fireEvent('autosize', this, w);
    }
});

Ext.override(Ext.Component, {
	onRender : function(ct, position){
        if(!this.el && this.autoEl){
            if(Ext.isString(this.autoEl)){
                this.el = document.createElement(this.autoEl);
            }else{
                var div = document.createElement('div');
                Ext.DomHelper.overwrite(div, this.autoEl);
                this.el = div.firstChild;
            }
            if (!this.el.id) {
                this.el.id = this.getId();
            }
        }
        if(this.el){
            this.el = Ext.get(this.el);
            if(this.allowDomMove !== false){
                ct.dom.insertBefore(this.el.dom, position);
                if (div) {
                    Ext.removeNode(div);
                    div = null;
                }
            }
        }
    }
});
// ************************* improve **********************

(function(){
	Ext.util.TextMetrics.Instance = function(bindTo, fixedWidth){
		var ml = new Ext.Element(document.createElement('div'));
		document.body.appendChild(ml.dom);
		ml.position('absolute');
		ml.setLeftTop(-1000, -1000);
		ml.hide();

		if(fixedWidth){
			ml.setWidth(fixedWidth);
		}

		var instance = {
			getSize : function(text){
				ml.update(text);
				var s = ml.getSize();
				ml.update('');
				return s;
			},
			bind : function(el){
				ml.setStyle(
					Ext.fly(el).getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
				);
			},
			setFixedWidth : function(width){
				ml.setWidth(width);
			},
			getWidth : function(text){
				ml.dom.style.width = 'auto';
				return this.getSize(text).width;
			},
			getHeight : function(text){
				return this.getSize(text).height;
			},
			// Add by clue
			destroy	:	function(){
				Ext.destroy(ml);
				delete ml;
			}
		};

		instance.bind(bindTo);

		return instance;
	};
})();
// ******************************* Memory Release *****************************

Ext.override(Ext.Component,{ 
	onDestroy	:	function(){ 
		if(this.plugins){
			Ext.destroy(this.plugins);
		}
		Ext.destroy(this.el);
	}
});

Ext.override(Ext.Panel,{
    onDestroy : function(){
		Ext.destroy(
            this.header,
            this.tbar,
            this.bbar,
            this.footer,
            this.body,
            this.bwrap,
			this.dd
        );
		Ext.Panel.superclass.onDestroy.call(this);
    }
});

Ext.override(Ext.dd.DragDrop,{ 
	onDestroy	:	Ext.emptyFn,
	destroy : function(){
		this.onDestroy();
        this.unreg();
    }
});

Ext.override(Ext.dd.DragSource,{ 
	onDestroy	:	function(){
		Ext.destroy(this.proxy);
		Ext.dd.DragSource.superclass.onDestroy.call(this);
	}
});

Ext.override(Ext.grid.GridDragZone,{
	onDestroy	:	function(){
		Ext.destroy(this.ddel);
		Ext.grid.GridDragZone.superclass.onDestroy.call(this);
	}
});

Ext.override(Ext.dd.StatusProxy,{ 
	onDestroy	:	Ext.emptyFn,
	destroy : function(){
		this.onDestroy();
		Ext.destroy(this.anim,this.el,this.ghost);
    }
});

Ext.override(Ext.grid.GridView,{
	destroy : function(){
        if(this.colMenu){
            Ext.menu.MenuMgr.unregister(this.colMenu);
            this.colMenu.destroy();
        }
        if(this.hmenu){
            Ext.menu.MenuMgr.unregister(this.hmenu);
            this.hmenu.destroy();
        }
		this.initData(null, null);
        this.purgeListeners();

        if(this.grid.enableColumnMove){
			delete Ext.dd.DDM.locationCache[this.columnDrag.id];
			Ext.destroy(this.columnDrag,this.columnDrop);
        }

        Ext.fly(this.innerHd).removeAllListeners();
        Ext.removeNode(this.innerHd);

		Ext.destroy(
            this.el,
            this.mainWrap,
            this.mainHd,
            this.scroller,
            this.mainBody,
            this.focusEl,
            this.resizeMarker,
            this.resizeProxy,
            this.activeHdBtn,
            this.dragZone,
            this.splitZone,
            this._flyweight
        );
        Ext.EventManager.removeResizeListener(this.onWindowResize, this);
    }
});

Ext.override(Ext.tree.TreePanel,{ 
	onDestroy : function(){
        if(this.rendered){
            this.body.removeAllListeners();
            Ext.dd.ScrollManager.unregister(this.body);
			Ext.destroy(this.dropZone,this.dragZone,this.innerCt);
        }
        Ext.destroy(this.root);
        Ext.tree.TreePanel.superclass.onDestroy.call(this);
    }
});

Ext.override(Ext.grid.HeaderDropZone,{ 
	onDestroy	:	function(){
		Ext.destroy(this.proxyTop,this.proxyBottom);
		Ext.grid.HeaderDropZone.superclass.onDestroy.call(this);
	}
});

Ext.override(Ext.menu.Menu,{
	onDestroy : function(){
        Ext.destroy(this.el);
        Ext.menu.MenuMgr.unregister(this);
        Ext.EventManager.removeResizeListener(this.hide, this);
        if(this.keyNav) {
            this.keyNav.disable();
        }
        var s = this.scroller;
        if(s){
            Ext.destroy(s.topRepeater, s.bottomRepeater, s.top, s.bottom);
        }
		this.purgeListeners();
		Ext.menu.Menu.superclass.onDestroy.call(this);
    }
});

Ext.override(Ext.TabPanel,{ // Ext.Panel
	onDestroy	:	function(){
		Ext.destroy(
			this.stack,
			this.stripWrap,
			this.stripSpacer,
			this.strip,
			this.edge,
			this.leftRepeater,
			this.rightRepeater
		);
		Ext.TabPanel.superclass.onDestroy.call(this);
	}
});

Ext.override(Ext.layout.ColumnLayout,{ 
	destroy	:	function(){
		Ext.destroy(this.innerCt);
	}
});

Ext.override(Ext.form.Field,{ 
	onDestroy	:	function(){
		Ext.destroy(this.errorEl,this.errorIcon);
		Ext.form.Field.superclass.onDestroy.call(this);
	}
});

Ext.override(Ext.form.TextField,{
	onDestroy	:	function(){
		Ext.destroy(this.metrics);
		if(this.validationTask){
			this.validationTask.cancel();
			this.validationTask = null;
		}
		Ext.form.TextField.superclass.onDestroy.call(this);
	}
});
2 楼 clue 2009-09-11  
继续抛砖。。。

今天继续尝试的时候,发现之前没注意到的问题。
释放变量和dom节点,可以选择在destroy/beforeDestroy/onDestroy中进行。
我一直是直接写在destroy方法中
但修改到Ext.Container中,释放this.items,this.layout,this.ownerCt这3个变量时,发现孤立dom节点更多了。
Ext.override(Ext.Container,{ // Ext.BoxComponent
	destroy	:	function(){ // add
		delete this.items;
		delete this.layout;
		delete this.ownerCt;
		Ext.Container.superclass.destroy.call(this);
	}
});

后面一个个测试发现,Ext.Container中定义的beforeDestroy实际上是在定义的destroy方法之后执行。
由于destroy已经直接解除items引用,导致里面的内容没有经过析构,从而引发了孤立节点。
因为,beforeDestroy的运行,是在Ext.Component的destroy中进行的:
    destroy : function(){
        if(this.fireEvent('beforedestroy', this) !== false){
            this.beforeDestroy();
            if(this.rendered){
                this.el.removeAllListeners();
                this.el.remove();
                if(this.actionMode == 'container' || this.removeMode == 'container'){
                    this.container.remove();
                }
            }
            this.onDestroy();
            Ext.ComponentMgr.unregister(this);
            this.fireEvent('destroy', this);
            this.purgeListeners();
        }
    },

而对destroy进行继承后,实际上是先执行Ext.Container.destroy,到后面再执行Ext.Container.superclass.destroy
从而使得出现了这种现象

为了避免再出现这种顺序问题,可以在Ext.Container.destroy开始就执行superclass.destroy
或者,改为重写beforeDestroy。

Ext自身好像没有规划得很明确,什么时候该放在beforeDestroy中,什么时候放在onDestroy中,什么时候直接继承destroy。


所以暂时想了一个规则,用来避免再次发生这种问题:
基类中销毁直接定义destroy方法,并调用onDestroy(如果没有,可设置为Ext.emptyFn)
子类中继承onDestroy,并执行完自己的工作后调用父类的onDestroy(和C++里析构函数执行顺序一样)
1 楼 fireinjava 2009-09-11  
楼主很有心 支持下

相关推荐

    基于Ext的单页系统设计与研究.pdf

    这篇文章主要探讨了如何利用Ext框架设计和实现一个单页系统(One Page Application,OPOA),这是一种优化用户体验的Web应用模式,旨在提高响应速度和用户交互性。文章首先介绍了Ext和单页系统的概念。 **1. Ext...

    mat-opoa:启动单页应用的mat插件

    mat-opoa Installation npm install --save-dev mat-opoa Usage var mat = require('mat'); var rap = require('mat-rap'); var opoa = require('mat-opoa'); // rap mock数据环境 mat.task('default', function () ...

    单页应用前端MVC框架Magix.zip

    包括:采取Dom节点即用即释放的方法,保障永不持有Dom节点采用全新的事件代理方案,高效解耦Dom节点与事件响应体Magix 基于“约定大于配置”设计原则,可以快速构建可扩展的大型单页面Web应用,同时也特别注意 ...

    extjs得单页系统实例

    OPOAdemo.rar extjs得单页系统实例

    asp.net与extjs开发点卡在线销售系统

    整个系统都使用了AJAX(Asynchronous JavaScript and XML)技术,与服务器交互采用异步方式,真正实现了OPOA(One Page One Application)单页程序。减少了用户等待的时间,抛弃了传统的B/S那种,提交>等待>刷新。

    jquery.load 是无法替代 iframe的

    `opoa.txt`文件没有明确的上下文,可能是日志、配置文件或其他文本信息。 总结来说,jQuery的`load()`方法和iframe各有其特点和适用场景,不能简单地互相替换。理解它们的区别,并根据项目需求选择合适的技术,是...

    网易企业邮箱5.0版创新功能全剖析.docx

    OPOA框架确保了代码的优化,而自定义组件库则使得邮箱的功能能够快速扩展和定制。这些技术的结合,使得邮件到达速度加快,通信协议得到优化,页面加载速度提高了35%。 智能动态组件系统是网易企业邮箱5.0版的另一大...

    支付宝 html5

    2012年10月,随着iPad等平板设备的普及,支付宝开始重视高分辨率屏幕的显示效果,采用了CSS3特效来提升视觉体验,并引入了OPOA(Object Pooling Object Adapter)模式,优化了资源管理和页面渲染效率。此外,...

    淘宝UED前端技术系列课程-NO.1 淘宝前端技术巡礼

    此外,原有的库和框架如YUI2/Tbra已无法满足需求,需要进行本地化的改进。 - **技术创新**:这一时期,前端开发开始采用栅格系统(Grid.css)来优化页面布局,并引入了一系列工具如Jslint、YC、Ant等来进行代码质量和...

    Javascript开发之js压缩篇.pdf

    作者引入了OPOA组件式开发方法,但是这种方法会增加js文件的总量。为了解决这个问题,作者推荐使用yui-compressor对js文件进行压缩混淆。 yui-compressor是Yahoo!开发的一款js压缩工具,可以将js文件压缩到原来的1/...

    5最值系列之阿氏圆问题.doc

    2. △OBP∽△OPA,即 OBOPOPOA=,变形为 2OPOA OB=×. 3. OPOBPAkOAOPPB===. 这些性质可以应用于解决各种问题,如确定圆心和半径、求解点的坐标等。 四、练习题和解答 我们提供了四个练习题和解答: 1. 已知 A...

    基于 HTML5 移动 Web App 开发.pdf

    例如,Android平台上的Web App方案会针对WebView组件进行优化和扩展,利用Webkit引擎提升性能,并通过界面优化技术如OPOA(Offscreen Page Cache)减少页面刷新,提升响应速度。 然而,HTML5技术目前仍存在成熟度...

    magix:Magix 旨在构建大规模、复杂的交互应用程序

    Magix npm install magix 简介 Magix适合用来构建大型的、交互复杂的应用。应用可以是前后端分离的单页应用,也可以是传统的交互复杂的页面。 Magix通过特有的vframe(类似iframe的思路)帮你把页面按区块化拆分(这里...

Global site tag (gtag.js) - Google Analytics