错误代码分析
内存泄露及解决方案
阻止基类进行清理
在子类的清理程序中,忽略了调用基类的清理程序。
Ext.define('Foo.bar.CustomButton', {
extend: 'Ext.button.Button',
onDestroy: function () {
// do some cleanup
}
});
解决方案:确保调用 callParent()
方法。
未移除DOM事件监听
DOM元素添加了事件处理,通过innerHTML
改变了元素。但是元素的事件处理驻留再内存中。
Ext.fly(someElement).on('click', doSomething);
someElement.parentNode.innerHTML = '';
解决方案:保留重要元素的引用,如果不需要该元素的时候调用其 destroy()
方法。
一直引用对象
创建了一个类的实例,并且使用了大量的内存。该实例被销毁,但是存在的对象上保留了对该实例的引用。
Ext.define('MyClass', {
constructor: function() {
this.foo = new SomeLargeObject();
},
destroy: function() {
this.foo.destroy();
}
});
this.o = new MyClass();
o.destroy();
// `this` still has a reference to `o`
// and `o` has a reference to `foo`.
解决方案:设置引用为 null
,确保内存被释放。在这个示例中,在清理方法内设置this.foo = null
, 调用o.destroy()
后,设置 this.o = null
。
闭包引用
这种情况更微妙(subtle),与上述的情况相似。闭包包含对一个大对象的引用,当闭包还在被引用的时候,不能释放大对象占用的内存。
function runAsync(val) {
var o = new SomeLargeObject();
var x = 42;
// other things
return function() {
// o is in closure scope but not needed
return x;
}
}
var f = runAsync(1);
因为大对象在作用域外定义,在内部函数中并未使用,这类情况很容易被忽略,但是对内存影响很大。
解决方案:在该函数体外定义函数,然后使用 Ext.Function.bind()
方法,或者标准 JavaScript 函数bind
创建安全的闭包。
function fn (x) {
return x;
}
function runAsync(val) {
var o = new SomeLargeObject();
var x = 42;
// other things
// o is not captured
return Ext.Function.bind(fn, null, [x]);
}
var f = runAsync(1);
持续创建对象实例
创建对象要付出一定的代价(例如:创建DOM元素)。如果创建了但是没销毁就会导致内存泄露。
{
xtype: 'treepanel',
listeners: {
itemclick: function(view, record, item, index, e) {
// Always creating and rendering a new menu
new Ext.menu.Menu({
items: [record.get('name')]
}).showAt(e.getXY());
}
}
}
解决方案:保留对menu
的引用,不再需要menu
时调用其destroy()
方法进行销毁。
清理缓存项
切记删除对一个对象的所有引用。只设置当前引用为 null
还不够。如果一些全局的单例缓存还在引用该对象,那么其引用在应用的生命周期内将一直存在。
var o = new SomeLargeObject();
someCache.register(o);
// Destroy and null the reference. someCache still has a reference
o.destroy();
o = null;
解决方案:确保从所有缓存中移除对象后再调用其destroy()
方法。