這篇文章主要介紹了關於如何解決JS高程中的垃圾回收機制與常見內存洩露的問題,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下
起因是因為想了解閉包的記憶體外洩機制,然後想起《js高階程式設計》中有關於垃圾回收機制的解析,之前沒有很懂,過一年回頭再看就懂了,寫給大家分享一下。如果喜歡的話可以按波贊/關注,支持一下。
分配你所需要的記憶體:
由於字串、對象等等沒有固定的大小,js程式在每次創建字串、物件的時候,程式都會分配記憶體來儲存那個實體。
使用分配到的記憶體做點什麼。
不需要時將其釋放回歸:
在不需要字串、物件的時候,需要釋放其所佔用的內存,否則將會消耗完系統中所有可用的內存,造成系統崩潰,這就是垃圾回收機制所存在的意義。
所謂的記憶體洩漏指的是:由於疏忽或錯誤造成程式未能釋放那些已經不再使用的內存,造成記憶體的浪費。
在C和C 之類的語言中,需要手動來管理記憶體的,這也是造成許多不必要問題的根源。幸運的是,在編寫js的過程中,記憶體的分配以及記憶體的回收完全實現了自動管理,我們不用操心這種事情。
垃圾收集器會按照固定的時間間隔,週期性的找出不再繼續使用的變量,然後釋放其佔用的記憶體。
什麼叫不再繼續使用的變數?
不再使用的變數也就是生命週期結束的變量,是局部變量,局部變數只在函數的執行過程中存在,當函數運行結束,沒有其他引用(閉包),那麼該變數會被標記回收。
全域變數的生命週期直到瀏覽器卸載頁面才會結束,也就是說全域變數不會被當成垃圾回收。
工作原理:
當變數進入環境時(例如在函數中宣告一個變數),並將這個變數標記為“進入環境”,當變數離開環境時,則標記為“離開環境”。標記「離開環境」的就回收記憶體。
工作流程:
垃圾收集器會在運行的時候會給儲存在記憶體中的所有變數都加上標記。
去掉環境中的變數以及被環境中的變數所引用的變數的標記。
那些還有標記的變數被視為準備刪除的變數。
最後一個垃圾收集器會執行最後一步記憶體清除的工作,銷毀那些帶有標記的值並回收它們所佔用的記憶體空間。
到2008年為止,IE、Chorme、Fireofx、Safari、Opera 都使用標記清除式的垃圾收集策略,只不過垃圾收集的時間間隔互有不同。
循環引用:追蹤記錄每個值被引用的技術
在舊版的瀏覽器中(對,又是IE),IE9以下BOM和DOM物件就是使用C 以COM物件的形式實現的。
COM的垃圾收集機制採用的就是引用計數策略,這種機制在出現循環引用的時候永遠釋放不掉記憶體。
var element = document.getElementById('something'); var myObject = new Object(); myObject.element = element; // element属性指向dom element.someThing = myObject; // someThing回指myObject 出现循环引用(两个对象一直互相包含 一直存在计数)。
解決方式是,當我們不使用它們的時候,手動切割連結:
myObject.element = null; element.someThing = null;
淘汰:
IE9把BOM和DOM對象轉向了真正的js對象,避免使用這種垃圾收集策略,消除了IE9以下常見的記憶體洩漏的主要原因。
IE7以下有一個宣告狼藉的效能問題,大家了解一下:
256個變量,4096個物件(或數組)字面或64KB的字串,達到任何一個臨界值會觸發垃圾收集器運作。
如果一個js腳本的生命週期一直保有那麼多變量,垃圾收集器會一直頻繁的運行,引發嚴重的效能問題。
IE7已修復這個問題。
雖然有垃圾回收機制,但我們在寫程式碼的時候,有些情況還是會造成記憶體洩漏,了解這些情況,並在編寫程式的時候,注意避免,我們的程式會更具健全性。
上文我們提到了全域變數不會被當成垃圾回收,我們在編碼中有時會出現下面這種情況:
function foo() { this.bar2 = '默认绑定this指向全局' // 全局变量=> window.bar2 bar = '全局变量'; // 没有声明变量 实际上是全局变量=>window.bar } foo();
当我们使用默认绑定,this会指向全局,this.something
也会创建一个全局变量,这一点可能很多人没有注意到。
解决方法:在函数内使用严格模式or细心一点
function foo() { "use strict"; this.bar2 = "严格模式下this指向undefined"; bar = "报错"; } foo();
当然我们也可以手动释放全局变量的内存:
window.bar = undefined delete window.bar2
当不需要setInterval
或者setTimeout
时,定时器没有被clear,定时器的回调函数以及内部依赖的变量都不能被回收,造成内存泄漏。
var someResource = getData(); setInterval(function() { var node = document.getElementById('Node'); if(node) { node.innerHTML = JSON.stringify(someResource)); // 定时器也没有清除 } // node、someResource 存储了大量数据 无法回收 }, 1000);
解决方法: 在定时器完成工作的时候,手动清除定时器。
闭包可以维持函数内局部变量,使其得不到释放,造成内存泄漏。
function bindEvent() { var obj = document.createElement("XXX"); var unused = function () { console.log(obj,'闭包内引用obj obj不会被释放'); }; // obj = null; }
解决方法:手动解除引用,obj = null
。
就是IE9以下的循环引用问题,上文讲过了。
var refA = document.getElementById('refA'); document.body.removeChild(refA); // dom删除了 console.log(refA, "refA"); // 但是还存在引用 能console出整个p 没有被回收
不信的话,可以看下这个dom。
解决办法:refA = null;
过多的console,比如定时器的console会导致浏览器卡死。
解决:合理利用console,线上项目尽量少的使用console,当然如果你要发招聘除外。
记住一个原则:不用的东西,及时归还,毕竟你是'借的'嘛。
减少不必要的全局变量,使用严格模式避免意外创建全局变量。
在你使用完数据后,及时解除引用(闭包中的变量,dom引用,定时器清除)。
组织好你的逻辑,避免死循环等造成浏览器卡顿,崩溃的问题。
即使是1byte的内存,也叫内存泄漏,并不一定是导致浏览器崩溃、卡顿才能叫做内存泄漏。
一般是堆区内存泄漏,栈区不会泄漏。
基本类型的值存在内存中,被保存在栈内存中,引用类型的值是对象,保存在堆内存中。所以对象、数组之类的,才会发生内存泄漏。
使用chorme监控内存泄漏,可以看一下这篇文章
了解了内存泄漏的原因以及出现的情况,那么我们在编码过程中只要多加注意,就不会发生非常严重的内存泄漏问题。
以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!
相关推荐:
原生JS基于window.scrollTo()封装垂直滚动动画工具函数
以上是如何解決JS高程中的垃圾回收機制與常見記憶體外洩的問題的詳細內容。更多資訊請關注PHP中文網其他相關文章!