JavaScript是使用垃圾回收的語言,也就是說執行環境負責在代碼執行時管理內存。在C和C++等語言中,跟蹤內存使用對開發者來說是很大的負擔,也是很多問題的來源。JavaScript為開發者卸下了這個負擔,通過自動內存管理實現內存分配和閑置資源回收。基本思路很簡單:確定哪個變量不會再使用,然后釋放它占用的內存。這個過程是周期性的,即垃圾回收程序每隔一定時間(或者說在代碼執行過程中某個預定的收集時間)就會自動運行。垃圾回收過程是一個近似且不完美的方案,因為某塊內存是否還有用,屬于不可判定的問題。意味著算法是解決不了的。
我們以函數中局部變量的正常生命周期為例。函數中的局部變量會在函數執行時存在。此時,棧(或者堆)內存會分配空間以保存相應的值。函數在內部使用了變量,然后退出。此時,就不再需要那個局部變量了,它占用的內存可以釋放,供以后使用。這種情況下顯然不再需要局部變量了,但并不是所有時候都會這么明顯。垃圾回收程序必須跟蹤記錄哪個變量還會使用,以及哪個變量不會再使用,以便回收內存。如何標記未使用的變量也許有不同的實現方式。不過,在瀏覽器的發展史上,用到過兩種主要的標記策略:標記清理和引用計數。
節選自JavaScript高級程序設計(第四版)第四章
1、引用計數(reference counting)
另一種不太常見的垃圾回收策略是引用計數。引用計數的含義是跟蹤記錄每個值被引用的次數。當聲明了一個變量并將一個引用類型賦值給該變量時,則這個值的引用次數就是1。相反,如果包含對這個值引用的變量又取得了另外一個值,則這個值的引用次數就減1。當這個引用次數變成0時,則說明沒有辦法再訪問這個值了,因而就可以將其所占的內存空間給收回來。這樣,垃圾收集器下次再運行時,它就會釋放那些引用次數為0的值所占的內存。
引用計數有個最大的問題: 循環引用。
比如對象A有一個屬性指向對象B,而對象B也有有一個屬性指向對象A,這樣相互引用.
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元素的內存永遠不會被回收,即使它已經被從頁面上刪除了也是如此。
為避免類似的循環引用問題,應該在確保不使用的情況下切斷原生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點半,大半夜寫的文章呢。
公眾號:小何成長,佛系更文,都是自己曾經踩過的坑或者是學到的東西
有興趣的小伙伴歡迎關注我哦,我是:何小玍。 大家一起進步鴨
、垃圾回收機制-GC
javascript具有自動垃圾回收機制,也就是說,執行環境會負責管理代碼執行過程中使用的內存。
原理:垃圾收集器會定期(周期性)找出那些不在繼續使用的變量垃圾,然后釋放期內存。
javascript垃圾回收的機制很簡單:找出不在使用的變量,然后釋放其占用的內存,但是這個過程不是實時的,應為其開銷比較大,所以垃圾回收器會按照固定時間間隔性的執行。
不在使用的變量也就是什么周期結束的變量,單人只可能是局部變量,全局變量的生命周期直至瀏覽器卸載頁面才會結束。局部變量只在函數的執行過程中存在,而在這個過程中會為局部變量在棧或堆上分配相應的空間,以儲存他們的值,然后在函數中使用這些變量,直至函數結束,而閉包中由于內部的函數原因,外部函數并不能算是結束。
還是上代碼說明吧:
function fn1(){
var obj={name:'司徒分享',age:'10' }
}
function fn2(){
var obj={name:'司徒分享',age:'20'}
return obj;
}
var a=fn1();
var b=fn2();
我們在看代碼是如何執行的。首先定義倆個function,分別叫做fn1和fn2,當fn1被調用時,進入fn1的環境,會開辟一塊內存存放對象。而當調用結束后,出了fn1的環境,那么該塊內存會被js引擎中的垃圾回收機制自動釋放;以備將來回收其內存,通常情況下有倆種實現方式,標記清除和引用計數。引用計數不太常用,標記清除較為常用。
二、引用計數
三、標記清除
四、內存管理
猶豫時間原因、這三點后期補充!
用過Windows系統的朋友都知道,在Windows中有回收站功能。如果有文件不小心刪除的話,可以通過回收站找回,但是安卓系統中卻沒有提供這個功能。如果我們不小心將文件刪除,只能通過第三方還原軟件來恢復文件,非常麻煩。其實我們可以安裝一款名字為Dumpster的應用,利用它就可以給自己的安卓手機安裝上一個回收站,如果不小心刪除某個文件,可以輕松將它找回,非常方便。
首先將Dumpster安裝到自己的安卓手機上,然后進行簡單的設置,設置哪些類型的文件啟動回收站功能,可以根據自己的實際情況來進行選擇。當選擇好后,就不用管理,讓Dumpster在后臺運行即可。
當我們將文件不小心刪除后,就可以啟動Dumpster,它會自動顯示攔截到的被刪除了的文件,并通過文件夾的形式顯示出來。你可以設置多種顯示方式,讓自己更輕松地找到想要恢復的文件,找到后選擇恢復,即可將誤刪掉的文件恢復到原來的位置。
Dumpster的原理跟Windows中的回收站原理一樣。我們將受保護的文件刪除,其實并不是真正意義上的物理刪除,只是將文件轉移到Dumpster指定的位置,所以隨時都可以進行還原。如果要是覺得文件真的沒有用,在Dumpster中將文件進行徹底刪除,從而釋放安卓手機的空間。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。