這是我參與8月更文挑戰的第8天,活動詳情查看:8月更文挑戰javascript
JavaScript是使用垃圾回收的語言,也就是說執行環境負責在代碼執行時管理內存。在C和C++等語言中,跟蹤內存使用對開發者來講是很大的負擔,也是不少問題的來源。JavaScript爲開發者卸下了這個負擔,經過自動內存管理實現內存分配和閒置資源回收。基本思路很簡單:肯定哪一個變量不會再使用,而後釋放它佔用的內存。這個過程是週期性的,即垃圾回收程序每隔必定時間(或者說在代碼執行過程當中某個預約的收集時間)就會自動運行。垃圾回收過程是一個近似且不完美的方案,由於某塊內存是否還有用,屬於不可斷定的
問題。意味着算法是解決不了的。java
咱們以函數中局部變量的正常生命週期爲例。函數中的局部變量會在函數執行時存在。此時,棧(或者堆)內存會分配空間以保存相應的值。函數在內部使用了變量,而後退出。此時,就再也不須要那個局部變量了,它佔用的內存能夠釋放,供之後使用。這種狀況下顯然再也不須要局部變量了,但並非全部時候都會這麼明顯。垃圾回收程序必須跟蹤記錄哪一個變量還會使用,以及哪一個變量不會再使用,以便回收內存。如何標記未使用的變量也許有不一樣的實現方式。不過,在瀏覽器的發展史上,用到過兩種主要的標記策略:標記清理
和引用計數
。node
節選自JavaScript高級程序設計(第四版)第四章算法
另外一種不太常見的垃圾回收策略是引用計數。引用計數的含義是跟蹤記錄每一個值被引用的次數。當聲明瞭一個變量並將一個引用類型賦值給該變量時,則這個值的引用次數就是1。相反,若是包含對這個值引用的變量又取得了另一個值,則這個值的引用次數就減1。當這個引用次數變成0時,則說明沒有辦法再訪問這個值了,於是就能夠將其所佔的內存空間給收回來。這樣,垃圾收集器下次再運行時,它就會釋放那些引用次數爲0的值所佔的內存。數組
引用計數有個最大的問題: 循環引用。瀏覽器
好比對象A有一個屬性指向對象B,而對象B也有有一個屬性指向對象A,這樣相互引用.markdown
function func() {
let obj1 = {};
let obj2 = {};
obj1.a = obj2; // obj1 引用 obj2
obj2.a = obj1; // obj2 引用 obj1
}
複製代碼
在這個例子中,objA和objB經過各自的屬性相互引用;也就是說這兩個對象的引用次數都是2。在採用引用計數的策略中,因爲函數執行以後,這兩個對象都離開了做用域,函數執行完成以後,objA和objB還將會繼續存在,由於他們的引用次數永遠不會是0。這樣的相互引用若是說很大量的存在就會致使大量的內存泄露。數據結構
解決:手動解除引用閉包
obj1.a = null;
obj2.a = null;
複製代碼
let element = document.getElementById('some_element')
let myObject = new Object()
myObject.element = element
element.someObject = myObject
複製代碼
這個例子在一個DOM對象(element)
和一個原生JavaScript對象(myObject)
之間製造了循環引用。myObject變量
有一個名爲element
的屬性指向DOM對象element
,而element對象
有一個someObject
屬性指回myObject對象
.因爲存在循環引用,所以DOM元素
的內存永遠不會被回收,即便它已經被從頁面上刪除了也是如此。dom
爲避免相似的循環引用問題,應該在確保不使用的狀況下切斷原生JavaScript對象
與DOM元素
之間的鏈接。好比,經過如下代碼能夠清除前面例子中創建的循環引用:
myObject.element = null
element.someObject = null
複製代碼
把變量設置爲null實際上會切段變量與其以前引用值之間的關係。當下次垃圾回收程序運行時,這些值就會被刪除,內存也會被回收。
注意⚠️:爲了補救這點,IE9把BOM和DOM對象都改爲了JavaScript對象,這同時也避免了因爲存在兩套垃圾回收算法而致使的問題,還消除了常見的內存泄漏現象。
這是javascript中最經常使用的垃圾回收方式。當變量進入執行環境是,就標記這個變量爲「進入環境」。從邏輯上講,永遠不能釋放進入環境的變量所佔用的內存,由於只要執行流進入相應的環境,就可能會用到他們。當變量離開環境時,則將其標記爲「離開環境」。
垃圾收集器在運行的時候會給存儲在內存中的全部變量都加上標記。而後,它會去掉環境中的變量以及被環境中的變量引用的標記。而在此以後再被加上標記的變量將被視爲準備刪除的變量,緣由是環境中的變量已經沒法訪問到這些變量了。最後。垃圾收集器完成內存清除工做,銷燬那些帶標記的值,並回收他們所佔用的內存空間。
標記清除也會遇到循環引用的問題。IE中有一部分對象並非原生JavaScript對象。例如,其BOM和DOM中的對象就是使用C++以COM(Component Object Model,組件對象)對象的形式實現的,而COM對象的垃圾回收器就是採用的引用計數的策略。所以,即便IE的Javascript引擎使用標記清除的策略來實現的,但JavaScript訪問的COM對象依然是基於引用計數的策略的。說白了,只要IE中涉及COM對象,就會存在循環引用的問題。
解決:手工斷開js對象和DOM之間的連接。賦值爲null。IE9把DOM和BOM轉換成真正的JS對象了,因此避免了這個問題。
經過上面內容瞭解了,瀏覽器雖然能夠自動化執行垃圾回收,但若是項目比較大代碼複雜,回收執行代價較大,某些狀況甚至不能識別回收
將[]
賦值給一個數組對象,是清空數組的捷徑(例如: arr = [];),可是須要注意的是,這種方式又建立了一個新的空對象,而且將原來的數組對象變成了一小片內存垃圾!實際上,將數組長度賦值爲0(arr.length = 0)也能達到清空數組的目的,而且同時能實現數組重用,減小內存垃圾的產生。
對象儘可能複用,尤爲是在循環等地方出現建立新對象,能複用就複用。不用的對象,儘量設置爲null,儘快被垃圾回收掉。
在循環中的函數表達式,能複用最好放到循環外面。
function fn(arg) {
m = "this is a hidden global variable"
}
複製代碼
m
沒被聲明,會變成一個全局變量,在頁面關閉以前不會被釋放。
另外一種意外的全局變量可能由 this
建立:
function fn() {
this.variable = "potential accidental global"
}
// fn 調用本身,this 指向了全局對象(window)
fn()
複製代碼
在 JavaScript 文件頭部加上 'use strict'
,能夠避免此類錯誤發生。啓用嚴格模式
解析 JavaScript ,避免意外的全局變量
。
let someResource = getData()
setInterval(function() {
let node = document.getElementById('Node')
if(node) {
// 處理 node 和 someResource
node.innerHTML = JSON.stringify(someResource))
}
}, 1000)
複製代碼
這樣的代碼很常見,若是id爲Node的元素
從DOM中移除
,該定時器仍會存在,同時,由於回調函數中包含對someResource的引用
,定時器外面的someResource
也不會被釋放。
因此要用完記住清除定時器鴨,也儘可能別在定時器裏引用dom對象。
function fn() {
let m = document.createElement('xx')
m.onClick = () => {
// Even if it a empty function
}
}
複製代碼
閉包能夠維持函數內局部變量,使其得不到釋放。
上例定義事件回調時,因爲是函數內定義函數,而且內部函數 -> 事件回調引用外部函數,造成了閉包
// 1. 將事件處理函數定義在外面
function fn() {
let m = document.createElement('xx')
m.onClick = onClickFn()
}
// 2. 定義事件處理函數的外部函數中,刪除對dom對象的引用
function fn() {
let m = document.createElement('xx')
m.onClick = () => {
// Even if it a empty function
}
m = null
}
複製代碼
將事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,刪除對dom的引用。
有時,保存 DOM 節點內部數據結構頗有用。假如你想快速更新表格的幾行內容,把每一行 DOM 存成字典(JSON 鍵值對)或者數組頗有意義。此時,一樣的 DOM 元素存在兩個引用:一個在 DOM 樹中
,另外一個在字典中
。未來你決定刪除這些行時,須要把兩個引用都清除
。
let elements = {
button: document.getElementById('button'),
image: document.getElementById('image'),
text: document.getElementById('text')
}
function doStuff() {
image.src = 'http://some.url/image'
button.click()
console.log(text.innerHTML)
}
function removeButton() {
document.body.removeChild(document.getElementById('button'))
// 此時,仍舊存在一個全局的 #button 的引用
// elements 字典。button 元素仍舊在內存中,不能被 GC 回收。
}
複製代碼
雖然咱們用removeChild
移除了button
,可是還在elements對象
裏保存着#button的引用
,換言之,DOM元素還在內存裏面
。
明天港性能,有空能夠來看看蛙,下班10點半,到家11點半,大半夜寫的文章呢。
公衆號:
小何成長
,佛系更文,都是本身曾經踩過的坑或者是學到的東西有興趣的小夥伴歡迎關注我哦,我是:
何小玍。
你們一塊兒進步鴨