摘要:的內(nèi)存管理是那些被稱作垃圾回收語言當中的一員。垃圾回收與內(nèi)存泄漏垃圾回收,簡稱。反例循環(huán)多次觸發(fā),效率太低在舊的瀏覽器上會導致內(nèi)存泄漏正解綁定事件的元素是不能在時被清理的,應該在之前取消事件綁定。
首先我們需要理解,內(nèi)存是什么。簡單來講,內(nèi)存存儲了計算機運行過程的需要的全部數(shù)據(jù),也就是計算機正在使用的全部數(shù)據(jù)。我們需要合理的使用內(nèi)存,防止內(nèi)存被大量無用數(shù)據(jù)占用,同時也要防止訪問和修改與當前程序無關的內(nèi)存區(qū)域。內(nèi)存主要包括以下幾個部分: 內(nèi)核數(shù)據(jù)區(qū)域,棧區(qū),共享庫映像,堆區(qū),可讀寫區(qū)域,只讀區(qū)域。學習javascript,我們不需要理解內(nèi)存和cache,內(nèi)存和I/O之間具體工作原理,但我們需要了解掌握如何合理的使用內(nèi)存,合理的分配釋放內(nèi)存。
javascript的內(nèi)存管理Javascript 是那些被稱作垃圾回收語言當中的一員。垃圾回收語言通過周期性地檢查那些之前被分配出去的內(nèi)存是否可以從應用的其他部分訪問來幫助開發(fā)者管理內(nèi)存。換句話說,當計算機發(fā)現(xiàn)有的內(nèi)存已經(jīng)不能被訪問到了,就會把它們標記為垃圾。開發(fā)者只需要知道一塊已分配的內(nèi)存是否會在將來被使用,而不可訪問的內(nèi)存可以通過算法確定并標記以便返還給操作系統(tǒng)。
引用傳遞和值傳遞js中的變量除了6個基本類型以外,其余的都是對象。也就說基本類型在賦值是傳遞的是值,也就是原來數(shù)據(jù)的一份拷貝?;绢愋桶╪umber、string、boolean、symbol、null、undefined.
用2個例子來理解一下:
var a = 10; //基本類型 var b = a; //a把10拷貝一份,把這個拷貝給b a = 20; //修改了a,不影響a的拷貝 console.log(a); //20 console.log(b); //10引用傳遞
var a = {num: 20}; //不是基本類型 var b = a; //這里沒有任何拷貝工作,b指向和a完全一致的同一塊內(nèi)存 b.num = 15; //由于b和a指向同一塊內(nèi)存,所以b.num修改了等同于a.num修改了 console.log(a.num); //15 console.log(b.num); //15 //進一步理解 b = {age: 10}; //等號右邊定義了一個新的對象,產(chǎn)生的新的內(nèi)存分配,此時b指向了這塊新的內(nèi)存,a還是指向原來那塊內(nèi)存 console.log(a); //{num: 15} console.log(b); //{age: 10}
值得強調(diào)的是:在函數(shù)參數(shù)傳遞的時候和返回值時一樣遵守這個傳遞規(guī)則,這是構(gòu)成閉包的基礎條件
簡單的函數(shù)傳遞參數(shù)//一個反例 var num1 = 10; var num2 = 20; function swap(a, b){ var temp = a; a = b; b = temp; } swap(num1, num2); console.log(num1); //10 console.log(num2); //20
以上代碼不能如愿的把2個傳入變量的值交換,因為基本類型在參數(shù)傳遞時也是值傳遞,及a,b是num1,num2的拷貝,不是num1和num2本身。當然實現(xiàn)交換的方法很多,在不引入第三個變量情況下,不用多帶帶寫一個函數(shù)。
//實現(xiàn)交換a,b //方法1: var temp = a; a = b; b =temp; //方法2: a = a + b; b = a - b; a = a - b; //方法3: a = [b, b = a][0]; //方法4(僅適用于整數(shù)交換): a = a ^ b; // ^表示異或運算 b = a ^ b; a = a ^ b; //方法5: [a, b] = [b, a]; //解構(gòu)賦值閉包的原理
var inc = function(){ var x = 0; return function(){ //返回一個非基本類型 console.log(x++); }; }; inc1 = inc(); //inc1是閉包內(nèi)匿名函數(shù)的引用,由于該引用存在,匿名函數(shù)引用計數(shù)不為0,所以inc作用域?qū)膬?nèi)存不能釋放,閉包形成 inc1(); //0 inc1(); //1淺拷貝與深拷貝
當對象的屬性是對象的時候,簡單地賦值導致改屬性傳遞的是另一個對象屬性的引用,這樣的拷貝是淺拷貝,存在安全風險。我們應該遞歸的拷貝對象屬性的每個對象,形成深拷貝。方法如下:
//淺拷貝與深拷貝 var o = { name: "Lily", age: 10, addr:{ city: "Shenzheng", province: "Guangdong" }, schools: ["primaryS", "middleS", "heightS"] }; var newOne = copy(o); console.log(o); //Object {name: "Lily", age: 10, addr: Object} console.log(newOne); //Object {name: "Lily", age: 10, addr: Object} newOne.name = "Bob"; console.log(newOne.name); //"Bob" console.log(o.name); //"Lily" newOne.addr.city = "Foshan"; console.log(newOne.addr.city); //"Foshan" console.log(o.addr.city); //"Foshan" function copy(obj){ var obj = obj || {}; var newObj = {}; for(prop in obj){ if(!obj.hasOwnProperty(prop)) continue; newObj[prop] = obj[prop]; //當obj[prop]不是基本類型的時候,這里傳的時引用 } return newObj; } var newOne = deepCopy(o); console.log(o); //Object {name: "Lily", age: 10, addr: Object} console.log(newOne); //Object {name: "Lily", age: 10, addr: Object} newOne.name = "Bob"; console.log(newOne.name); //"Bob" console.log(o.name); //"Lily" newOne.addr.city = "Foshan"; console.log(newOne.addr.city); //"Foshan" console.log(o.addr.city); //"Shenzheng" newOne.schools[0] = "primatrySchool"; console.log(newOne.schools[0]); //"primatrySchool" console.log(o.schools[0]); //"primatryS" function deepCopy(obj){ var obj = obj || {}; var newObj = {}; deeply(obj, newObj); function deeply(oldOne, newOne){ for(var prop in oldOne){ if(!oldOne.hasOwnProperty(prop)) continue; if(typeof oldOne[prop] === "object" && oldOne[prop] !== null){ newOne[prop] = oldOne[prop].constructor === Array ? [] : {}; deeply(oldOne[prop], newOne[prop]); } else newOne[prop] = oldOne[prop]; } } return newObj; }變量定義和內(nèi)存釋放
不同的變量定義方式,會導致變量不能被刪除,內(nèi)存無法釋放。
// 定義三個全局變量 var global_var = 1; global_novar = 2; // 反面教材 (function () { global_fromfunc = 3; // 反面教材 }()); // 試圖刪除 delete global_var; // false delete global_novar; // true delete global_fromfunc; // true // 測試該刪除 typeof global_var; // "number" typeof global_novar; // "undefined" typeof global_fromfunc; // "undefined"
很明顯,通過var定義的變量無法被釋放。
垃圾回收與內(nèi)存泄漏垃圾回收(Garbage Collection),簡稱GC。簡單來講,GC就是把內(nèi)存中不需要的數(shù)據(jù)釋放了,這樣這部分內(nèi)存就可以存放其他東西了。在javascript中,如果一個對象不再被引用,那么這個對象就會被GC回收。具體回收策略包括以下3種:
標記回收當從window節(jié)點遍歷DOM樹不能遍歷到某個對象,那么這個對象就會被標記為沒用的對象。由于回收機制是周期性執(zhí)行的,這樣,當下一個回收周期到來時,這個對象對應的內(nèi)存就會被釋放。
引用計數(shù)當系統(tǒng)中定義了一個對象后,對于這一塊內(nèi)存,javascript會記錄有多少個引用指向個部分內(nèi)存,如果這個數(shù)為零,則這部分內(nèi)存會在下一個回收周期被釋放。
手動釋放就好比上一個例子中,利用delete關鍵字刪除變量或?qū)傩?,達到釋放內(nèi)存的目的。分一下幾種情況:
//釋放一個對象 obj = null; //釋放是個對象屬性 delete obj.propertyName; delete globalVariable; //沒有用var聲明的變量是window的屬性,用delete釋放。 //釋放數(shù)組 array.length = 0; //釋放數(shù)組元素 array.splice(2,2); //刪除并釋放第三個元素起的2個元素
不過需要注意的是, 這幾個GC策略是同時作用的:
var o1 = {}; //開辟一塊內(nèi)存放置對象,并用o1指向它 var o2 = o1; //o2指向與o1同一個內(nèi)存區(qū)域 console.log(o1); //{} console.log(o2); //{} o2 = null; //標記o2為沒用的對象 console.log(o2); //null console.log(o1); //{} 由于還有o1指向這個內(nèi)存區(qū)域,引用計數(shù)不為零,所以內(nèi)存并沒有被釋放 o1 = null; //引用計數(shù)為0, 內(nèi)存釋放
如果你訪問了已經(jīng)被回收了的內(nèi)存,會發(fā)生不可預計的嚴重后果。比如一段內(nèi)存被釋放了,可能里面的值就不是原來的值了,你還要拿來用那不是自己找錯誤嗎?更嚴重的就是你修改了其他程序的數(shù)據(jù)?。。∥覀儗⑦@樣的變量叫做野指針(wild pointer)。為了避免這樣的也只能出現(xiàn),也為了節(jié)省計算機資源,我們需要防止內(nèi)存泄露(memory leak)。
內(nèi)存泄漏也稱作存儲滲漏,用動態(tài)存儲分配函數(shù)動態(tài)開辟的空間,在使用完畢后未釋放,結(jié)果導致一直占據(jù)該內(nèi)存單元,直到程序結(jié)束。簡單來說就是該內(nèi)存空間使用完畢之后未回收。
內(nèi)存泄露是每個開發(fā)者最終都不得不面對的問題。即便使用自動內(nèi)存管理的語言,你還是會碰到一些內(nèi)存泄漏的情況。內(nèi)存泄露會導致一系列問題,比如:運行緩慢,崩潰,高延遲,甚至一些與其他應用相關的問題。
可能導致內(nèi)存泄漏的操作清除所以子元素用innerHTML=""替代removeChild(),因為在sIEve中監(jiān)測的結(jié)果是用removeChild無法有效地釋放dom節(jié)點。
//反例 var parent = document.getElementById("parent"); var first = parent.firstChild(); while(first){ //循環(huán)多次觸發(fā)reflow,效率太低 parent.removeChild(first); //在舊的瀏覽器上會導致內(nèi)存泄漏 first = parent.firstChild(); } //正解 document.getElementById("parent").innerHTML = “”;
綁定事件的元素是不能在remove時被清理的,應該在remove之前取消事件綁定。不過更好的辦法是用事件委托的方式綁定事件。
var ele = document.getElementById("eleID"); ele.onclick = function fun(){ //Do stuff here } //... ele.onclick = null; //刪除元素前取消所有事件,jQuery中也是在刪除節(jié)點前利用removeEventListen去除了對應事件 ele.parentNode.removeChild(ele);
意外的全局變量,會使得實際函數(shù)結(jié)束就應該釋放的內(nèi)存保留下來,造成資源浪費,包括以下兩種情況:
在嚴格模式下編寫代碼可以避免這個問題
//情況一: 函數(shù)中沒有用var聲明的變量 function fun1(){ name = "Mary"; //全局變量 } fun1(); //情況二: 構(gòu)造函數(shù)沒用new關鍵字調(diào)用 function Person(name){ this.name = name; } Person("Mary"); //函數(shù)內(nèi)定義全局變量
定時器中的變量定義,會在每次執(zhí)行函數(shù)的時候重復定義變量,產(chǎn)生嚴重的內(nèi)存泄漏。
//反例 setInterval(function(){ var ele = document.getElementById("eleID"); //改代碼毎100毫秒會重復定義該引用 //Do stuff }, 100); //正解 setInterval(function(){ var ele = document.getElementById("eleID"); //改代碼毎100毫秒會重復定義該引用 //Do stuff ele = null; }, 100);
如果閉包的作用域鏈中保存著一個DOM對象或者ActiveX對象,那么就意味著該元素將無法被銷毀:
//反例 //不妨認為這里的上下文是window function init(){ var el = document.getElementById("MyElement"); //這是一個DOM元素的引用,非基本類型 el.onclick = function(){ //el.onclick是function匿名函數(shù)的引用 alert(el.innerHTML); //funciton中訪問了這個作用域以外的DOM元素引用el,導致el不能被釋放 } } init(); //正解 function init(){ var el = document.getElementById("MyElement"); //這是一個DOM元素的引用,是非基本類型 var text = el.innerHTML; //字符串,是基本類型,解決alert(el.innerHTML)不能正常工作問題 el.onclick = function(){ //el.onclick是function匿名函數(shù)的引用 alert(text); //var聲明的text是基本類型,不必釋放 } el = null; //手動釋放,但會導致alert(el.innerHTML)不能正常工作 } init(); //如果函數(shù)結(jié)尾要return el,用以下方法釋放el //正解 function init(){ var el = document.getElementById("MyElement"); //這是一個DOM元素的引用,是非基本類型 var text = el.innerHTML; //字符串,是基本類型,解決alert(el.innerHTML)不能正常工作問題 el.onclick = function(){ //el.onclick是function匿名函數(shù)的引用 alert(text); //var聲明的text是基本類型,不必釋放 } try{ return el; } finally { el = null; //手動釋放,但會導致alert(el.innerHTML)不能正常工作 } } init();
通過createElement,createTextNode等方法創(chuàng)建的元素會在寫入DOM后被釋放
function create() { var parent = document.getElementById("parent"); for (var i = 0; i < 5000; i++) { var el = document.createElement("div"); el.innerHTML = "test"; gc.appendChild(el); //這里釋放了內(nèi)存 } }
循環(huán)引用導致引用計數(shù)永遠不為0,內(nèi)存無法釋放:
//構(gòu)成一個循環(huán)引用 var o1 = {name: "o1"}; var o2 = {name: "o2"}; o1.pro = o2; o2.pro = o1; //這種情況需要手動清理內(nèi)存,在不需要的時候把對象置為null或刪除pro屬性
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/97463.html
摘要:內(nèi)存回收此時,局部變量就沒有存在的必要了,因此可以釋放它們的內(nèi)存以供將來使用。局部變量會在它們離開執(zhí)行環(huán)境時自動被解除引用,如下面這個例子所示手工解除的引用由于局部變量在函數(shù)執(zhí)行完畢后就離開了其執(zhí)行環(huán)境,因此無需我們顯式地去為它解除引用。 JavaScript 具有自動垃圾收集機制(GC:Garbage Collecation),也就是說,執(zhí)行環(huán)境會負責管理代碼執(zhí)行過程中使用的內(nèi)存。而...
摘要:摘要是如何回收內(nèi)存的深入淺出系列深入淺出第課箭頭函數(shù)中的究竟是什么鬼深入淺出第課函數(shù)是一等公民是什么意思呢深入淺出第課什么是垃圾回收算法最近垃圾回收這個話題非?;穑蠹也荒茈S隨便便的扔垃圾了,還得先分類,這樣方便對垃圾進行回收再利用。 摘要: JS是如何回收內(nèi)存的? 《JavaScript深入淺出》系列: JavaScript深入淺出第1課:箭頭函數(shù)中的this究竟是什么鬼? Jav...
摘要:本文詳細描述了堆內(nèi)存模型,垃圾回收算法以及處理內(nèi)存泄露的最佳方案,并輔之以圖表,希望能對理解內(nèi)存結(jié)構(gòu)有所幫助。該區(qū)域也稱為內(nèi)存模型的本地區(qū)。在中,內(nèi)存泄露是指對象已不再使用,但垃圾回收未能將他們視做不使用對象予以回收。 本文詳細描述了 Java 堆內(nèi)存模型,垃圾回收算法以及處理內(nèi)存泄露的最佳方案,并輔之以圖表,希望能對理解 Java 內(nèi)存結(jié)構(gòu)有所幫助。原文作者 Sumith Puri,...
摘要:的內(nèi)存限制和垃圾回收機制內(nèi)存限制內(nèi)存限制一般的后端語言開發(fā)中,在基本的內(nèi)存使用是沒有限制的。的內(nèi)存分代目前沒有一種垃圾自動回收算法適用于所有場景,所以的內(nèi)部采用的其實是兩種垃圾回收算法。 前言 從前端思維轉(zhuǎn)變到后端, 有一個很重要的點就是內(nèi)存管理。以前寫前端因為只是在瀏覽器上運行, 所以對于內(nèi)存管理一般不怎么需要上心, 但是在服務器端, 則需要斤斤計較內(nèi)存。 V8的內(nèi)存限制和垃圾回收機...
摘要:垃圾回收內(nèi)存管理實踐先通過一個來看看在中進行垃圾回收的過程是怎樣的內(nèi)存泄漏識別在環(huán)境里提供了方法用來查看當前進程內(nèi)存使用情況,單位為字節(jié)中保存的進程占用的內(nèi)存部分,包括代碼本身棧堆。 showImg(https://segmentfault.com/img/remote/1460000019894672?w=640&h=426);作者 | 五月君Node.js 技術棧 | https:...
摘要:內(nèi)存泄露內(nèi)存泄露概念在計算機科學中,內(nèi)存泄漏指由于疏忽或錯誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存。判斷內(nèi)存泄漏,以字段為準。 本文是 重溫基礎 系列文章的第二十二篇。 今日感受:優(yōu)化學習方法。 系列目錄: 【復習資料】ES6/ES7/ES8/ES9資料整理(個人整理) 【重溫基礎】1-14篇 【重溫基礎】15.JS對象介紹 【重溫基礎】16.JSON對象介紹 【重溫基礎】1...
閱讀 1008·2023-04-25 23:54
閱讀 3091·2021-11-08 13:21
閱讀 3881·2021-09-27 13:35
閱讀 3445·2021-07-26 23:41
閱讀 1094·2019-08-30 15:52
閱讀 3508·2019-08-30 11:27
閱讀 2157·2019-08-29 18:37
閱讀 613·2019-08-29 17:24