时间:2015-12-7 作者:剧中人
最近在某乎上回答了一个问题 JavaScript里如何实现自定义对象的ondestroy方法?,回答与题主期望有所偏差但勉强可以作为一点个人经验分享出来。
在日常的JS组件开发中,往往会有一些较为复杂的DOM
操作及事件监听,尤其是在处理UI层面的widgets
时候更为明显。常常会花很多精力在对象的init
上,而当组件需要被移除时则仅仅是把所在DOM
草草的remove
掉就算完事儿。
当然,绝大多数情况这样处理并没有什么不妥,因为事件监听时仅仅局限于所属的DOM
自身,移除DOM
后,只要对象的外部引用不再维系,相关的内存占用很快就会被当作垃圾回收掉(暂不讨论低版本 IE 内存回收的 BUG)。
其实个人在构建组件(对象)的时候是比较习惯于添加自定义方法destroy
,用来手动销毁对象内部的一些引用。也就是小剧今天要说的仅靠移除DOM
并不能达到销毁对象的几种情况。
当你的组件出现下面几种情况时需要特别注意。
常规情况下,一个组件需要监听的仅仅是自身的DOM
内的事件。偶尔也会有另一种情况,对象不得不操作自身之外的DOM
。
拿常见的瀑布流组件为例,除了自身事件,还要监听页面的滚动、浏览器尺寸重置等事件。因此当瀑布流组件需要被移除时,简单的移除自身DOM
并不能完整销毁组件对页面的影响。
下面是常规做法的例子:
//定义瀑布流组件
function WaterFall(node){
this.node = node;
window.addEventListener('scroll',function(){
//do sth
console.log('scrolling');
});
}
//实例化一个瀑布流组件
var node_content = document.getElementById('xxx');
new WaterFall(node_content);
//移除瀑布流组件所属的DOM
node_content.parentNode.removeChild(node_content);
上面的例子很明显,移除DOM
后遗留的对 window 的事件监听还在,回调内对组件的引用会导致整个组件常驻内存无法被回收,直至页面卸载。
不过你可能会说,在移除DOM
时顺手解除下事件绑定就 OK 啦。事实确实如此,但是如果操作的具体细节让调用者实现就有点儿麻keng烦die了。因此我们需要提供一个destroy
接口让调用者去解除对窗口滚动等事件的监听。
//定义瀑布流组件
function WaterFall(node){
this.node = node;
this._scrollListenner = function(){
//do sth
console.log('scrolling');
};
window.addEventListener('scroll',this._scrollListenner);
}
WaterFall.prototype.destroy = function(){
window.removeEventListener('scroll',this._scrollListenner);
this.node.parentNode.removeChild(this.node);
};
//实例化一个瀑布流组件
var myWaterFall = new WaterFall(document.getElementById('xxx'));
//注销瀑布流组件
myWaterFall.destroy();
一部分场景下,某段 JS 会在整个生命周期中反复被调用。比如轮播图自动播放,倒计时时钟的重绘。无论是使用setInterval
不断调取,或者是 setTimeout
递归延时。这两者在对象自身DOM
被移除时同样不会随之被清除。因此也需要对象在被销毁时手动解除定时器。
//定义倒计时组件
function Countdown(node){
this.node = node;
this._timerId = setInterval(function(){
//do sth
console.log('recount');
},500);
}
Countdown.prototype.destroy = function(){
clearInterval(this._timerId);
this.node.parentNode.removeChild(this.node);
};
//实例化一个倒计时组件
var myCountdown = new Countdown(document.getElementById('yyy'));
//注销倒计时组件
myCountdown.destroy();
比较常见的情形就是 ajax
。当一个请求结束之前对象被销毁,ajax
返回后的操作无需继续进行。也有一定风险因为 dom
已被移除导致操作报错。
以渲染数据为例,ajax
部分使用jquery
:
//定义列表组件
function UserList(node){
this.node = node;
this._ajax = $.ajax(/** **/);
}
UserList.prototype.destroy = function(){
//取消请求发送
this._ajax.abort();
this.node.parentNode.removeChild(this.node);
};
//实例化一个列表组件
var myUserList = new Countdown(document.getElementById('yyy'));
//注销列表组件
myUserList.destroy();
上面例子中,
destroy
方法内部移除DOM
只是为了说明方便,实际开发中一般不会这么做,只要做到移除元素完不成的那部分任务即可。
以上就是小剧总结的对象销毁时需要额外注意的三种情况,当然还有其他更多情况,但大体与之类似。
若您有兴趣,不妨花费几分钟的时间吐槽下小剧客栈单页面实现的部分代码github.com。新旧页面切换时,旧页面销毁部分的处理,正是这篇文章思路萌发的源头。
让代码有自我意识,从学会自我销毁开始。
完了。