一篇文章帶你搞懂JS物件的自我銷燬
在日常的JS元件開發中,往往會有一些較為複雜的DOM操作及事件監聽,尤其是在處理UI層面的widgets時候更為明顯。常常會花很多精力在物件的init上,而當元件需要被移除時則僅僅是把所在DOM草草的remove掉就算完事兒。
當然,絕大多數情況這樣處理並沒有什麼不妥,因為事件監聽時僅僅侷限於所屬的DOM自身,移除DOM後,只要物件的外部引用不再維繫,相關的記憶體佔用很快就會被當作垃圾回收掉(本文不討論低版本 IE 記憶體回收的 BUG)。
其實個人在構建元件(物件)的時候是比較習慣於新增自定義方法destroy,用來手動銷燬物件內部的一些引用。也就是今天要說的僅靠移除DOM並不能達到銷燬物件的幾種情況。
當你的元件出現下面幾種情況時需要特別注意。
一: 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後遺留的事件監聽還在,回撥內對元件的引用會導致整個元件常駐記憶體無法被回收,直至頁面解除安裝。
不過你可能會說,在移除DOM時順手解除下事件繫結就 OK 啦。事實確實如此,但是如果操作的具體細節讓呼叫者實現就有點兒麻keng煩die了。因此我們需要提供一個destroy介面讓呼叫者去解除對視窗滾動等事件的監聽。
//定義瀑布流元件 function WaterFall(node){ this.node = node; this._scrollListenner = function(){ //do sth console.log('scrolling'); }; window.addEventListener('scroll',this._scrollListenner); }//歡迎加入前端全棧開發交流圈一起學習交流:1007317281 WaterFall.prototype.destroy = function(){ window.removeEventListener('scroll',this._scrollListenner); this.node.parentNode.removeChild(this.node); }; //例項化一個瀑布流元件 var myWaterFall = new WaterFall(document.getElementById('xxx')); //登出瀑布流元件 myWaterFall.destroy();
給大家推薦一個技術交流學習圈,裡面概括移動應用網站開發,css,html,webpack,vue node angular以及面試資源等。 獲取資料 :point_left::point_left::point_left:
對web開發技術感興趣的同學,可以加入 :point_right::point_right::point_right:交流圈 :point_left::point_left::point_left: ,不管你是小白還是大牛都歡迎,還有大牛整理的一套高效率學習路線和教程與您免費分享,同時每天更新視訊資料。
二:JS 生命週期過長
一部分場景下,某段 JS 會在整個生命週期中反覆被呼叫。比如輪播圖自動播放,倒計時時鐘的重繪。無論是使用setInterval不斷調取,或者是 setTimeout遞迴延時。這兩者在物件自身DOM被移除時同樣不會隨之被清除。因此也需要物件在被銷燬時手動解除定時器。
//定義倒計時元件 function Countdown(node){ this.node = node; this._timerId = setInterval(function(){ //do sth console.log('recount'); },500); }//歡迎加入前端全棧開發交流圈一起學習交流:1007317281 Countdown.prototype.destroy = function(){ clearInterval(this._timerId); this.node.parentNode.removeChild(this.node); }; //例項化一個倒計時元件 var myCountdown = new Countdown(document.getElementById('yyy')); //登出倒計時元件 myCountdown.destroy();
三:DOM 之外的非同步事件
比較常見的情形就是 ajax。當一個請求結束之前物件被銷燬,ajax 返回後的操作無需繼續進行。也有一定風險因為 dom 已被移除導致操作報錯。
以渲染資料為例,ajax部分使用jquery:
//定義列表元件 function UserList(node){ this.node = node; this._ajax = $.ajax(/** **/); }//歡迎加入前端全棧開發交流圈一起學習交流:1007317281 UserList.prototype.destroy = function(){ //取消請求傳送 this._ajax.abort(); this.node.parentNode.removeChild(this.node); }; //例項化一個列表元件 var myUserList = new Countdown(document.getElementById('yyy')); //登出列表元件 myUserList.destroy();
上面例子中,destroy方法內部移除DOM只是為了說明方便,實際開發中一般不會這麼做,只要做到移除元素完不成的那部分任務即可。
以上就是總結的物件銷燬時需要額外注意的三種情況,當然還有其他更多情況,但大體與之類似。
讓程式碼有自我意識,從學會自我銷燬開始。