😔javascript
如下用一段代碼說明堆和棧的區別:html
後進先出結構前端
早高峯的電梯,擠滿了人,先進去的要想出來,後進去的是否是要先出來讓路?就是這個道理吧。。。java
這樣,要獲取其中一個,是否是很費性能。node
存放的數據類型:c++
String、Number、Boolean、Null、Undefined 這五種基礎數據類型。程序員
拷貝這些類型的數據就是拷貝一個副本面試
以及:算法
Object、Array、Function等引用類型的指針。數組
拷貝這些類型的數據是拷貝了指針一個副本,新指針和原指針仍是指向堆內存裏的同一個地址。
隊列是先進先出結構,它兩邊都有口。就像去火車站排隊買票。第一我的先排隊的,業務員第一個接待他。(業務員就是js主線程)
樹狀結構
能夠隨時獲取,就像書架上的書,也像蘋果樹上的每個蘋果,想摘那個摘哪一個。就能夠省點力氣(不像棧,想摘最高的那個,還得把最底下的摘完才能摘。。。)
存放的數據類型:
Object、Array、Function等引用類型、閉包的變量。
通常歸類到棧中,存放常量。
和學到這裏的你同樣,我常常搞不懂棧和調用棧究竟是真麼關係?棧空間和棧內存又是否是一回事兒?
直到我拜讀了阮一峯老師的博文,才漸漸清醒起來。
這裏記錄本身的讀書筆記。想觀摩原做的請跳轉至:Stack的三種含義
做爲一種數據的存放方式,特色是後進先出。
上邊電梯的例子在這裏不夠更加形象,
能夠想象成一摞疊在一塊兒的一元硬幣,假如一共十個,想要拿出最下邊那個,計算機的處理方式就是,把上邊九個依次拿開,
才能把第十個拿出來給你(不要想一塊兒把前九個擡起來拿最後一個的想法,那是你不是計算機。。。)。
這種作法,在js中有些方法組合使用就是了:
push + pop:從最後邊依次推動去,再從最後邊拿。上邊的1就是push(),2就是pop()的作法了。
調用棧,每個函數在調用的時候,都堆疊到一塊兒(和上邊的硬幣同樣),須要把上邊的先調用了,而後銷燬、出棧以後,再把下邊的暴露出來進行。
好比下邊這段代碼的執行:
這裏,foo()和bar()【包括window在js代碼】運行時,js引擎就會生成執行上下文(就是我們常說的函數做用域了),這一段理解了,就能繞過不少面試題的障礙。
後期會整理到這裏。等不及的你趕忙去看看吧。
存放數據的一種內存區域。
系統劃分出來的兩個內存空間:棧和堆
區別如上所說,
棧有結構,因此要按次序存放,也能夠知道每一個區的大小,超過大小就是棧溢出錯誤。一個線程分配一個stack,線程獨佔。運行結束棧被清空
堆沒有結構,能夠任意存放,大小也不能肯定,能夠按須要增長。每一個進程分配一個heap,線程共用。運行結束對象實例繼續存在,直到垃圾回收。若是沒被回收,就是內存泄漏。
(stack的尋址速度快於heap?)
就是咱們聲明一個變量、對象等的時候,系統就會自動給咱們的變量分配內存。
好比執行下邊的代碼:
var a = 10;
事實上,編譯器會這麼處理:(節選自《你不知道的js(上)》第一章 1.2.2 p7)
遇到 var a,編譯器會先像做用域(由於這裏是在全局做用域執行的,你能夠理解爲window)尋找是否已經有一個a存在同一個做用域集合(window對象)中。
若是有a,則忽略該聲明,繼續進行編譯。
不然沒有a,會要求做用域在當前做用域集合(即window對象)中聲明一個新的變量,並命名爲a。這個過程,就是內存分配。
就是編譯器讀、寫內存,調取變量/對象等的值的時候。
讀就是獲取變量值,寫入就是賦值或修改變量的值。這裏引入兩個《你不知道的js(上)》介紹的名詞
好比:
console.log(a)
引擎在這裏會有兩段RHS查找(獲得某某的值):
一、查找console
二、查找a的值
首先,console這個對象是在window對象上的一個屬性。log是他上邊的一個方法。
而後查找a的值,由於a被建立到了棧內存,對a的取值就是內存使用
當咱們使用完一個函數,該函數就會被自動銷燬。(不考慮閉包的狀況)
js中有垃圾回收機制,會自動回收再也不使用的內存。
var a = null;//使用完畢,自動釋放內存空間
不少語言,在使用完畢後須要程序員手動釋放內存,咱們應該慶幸的是,js引擎有自動的垃圾回收機制。能夠自動進行內存管理,減輕了咱們的工做負擔。
垃圾收集器每隔固定的時間就執行一次檢查,再也不使用的值就釋放其佔用的內存。
那麼問題是,垃圾回收機制怎麼知道哪些內存須要回收了呢?
引用計數 方法:
原理,就是引擎記錄全部對象值的引用次數,若是引用次數是0,就表示沒用了能夠「刪除」
那什麼樣纔算沒有引用了呢?
好比這裏js文件中只有一行代碼:
var a = [123,2];
你說a有引用嗎?
我第一感受是沒有的,可是看阮一峯大神的講解,這裏是還有引用的。
數組還在佔用內存,變量a是一個引用。因此數組[123,2]這個變量值的引用次數是1,不能被清除
要想解除他的引用,須要執行
a = null;這樣,a變成了一個null值,而數組[123,2]沒有人引用他了,下一輪垃圾清理器過來的時候就會把他清除了。
因此,這麼看來,咱們寫完程序後還要檢查有哪些值是再也不使用的,就給他指向null,剪斷引用的線,釋放內存空間。
尤爲是在全局做用域。由於局部函數做用域有可能在沒有閉包的狀況下,函數執行完畢就會被自動消除。可是全局的變量,系統很難判斷還有沒有用,就像上邊的a同樣,因此咱們除了避免使用全局變量外,還有記得及時釋放不得已創建的全局變量。
可是他也有他的缺點,我上邊列了腦圖。
出現循環引用的狀況:
var div = document.createElement("div"); div.onclick = function() { console.log("click"); };
變量div有事件處理函數的引用,同時事件處理函數也有div的引用!(div變量可在函數內被訪問)。一個循序引用出現了,按ie中用的引用計數算法,該部份內存無可避免地泄露了。
擴展:
ie8中,COM對象,用c++實現的組件對象模型,使用的就是引用計數方法。經常由於循環引用發生內存泄漏
標記清除 方法:(經常使用)
原理:對象是否可達。否,則被回收
從window全局對象根對象開始遍歷,按期向下查找,找全部從根開始引用的對象、這些對象引用的對象。而後就知道哪些是可達到的,哪些是不可達到的(個人理解是和其餘人沒有聯繫的)
能達到的添加標識,最後沒有標識的就會被內存回收,而且將以前的標記清除,下一次從新標記
這樣,在循環引用的狀況中,即便兩者彼此互幫互助循環引用防止垃圾清除,可是,標記清除法則從根元素開始找,找不到他倆,他倆就都被清除了。
2018-12-07 23:40:48
前文說道,若是我們創建的變量對象在不使用時沒有及時被回收,就會形成內存泄漏。
就是動態分配的空間,在使用完畢後沒有被釋放,就會致使該內存空間一直被白白佔用,直到程序結束。
危害:
想一想,電腦的空間就那麼多,一直有人「佔着茅坑不拉*」,內存空間就會愈來愈少,程序運行就會愈來愈慢,形成程序的卡頓效果,嚴重的甚至還可能致使系統的崩潰。
具體表現整理以下:(參見搜狗百科)
一、cpu資源耗盡
鼠標鍵盤等操做無反應、頁面假死
二、進程耗盡
沒有新的進程能夠用了,放到瀏覽器裏有Browser進程、插件進程、GPU進程、內核渲染進程等,若是進程耗盡,其中想開一個是否是就不可能了。
三、硬盤耗盡
機器崩潰
四、內存泄漏或者內存耗盡
很麻煩並且很差用工具定位和跟蹤 - 隱式內存泄漏
內存泄漏的分類:
常發性
偶發性
一次性
隱式:
說說這個和咱們前端有關係的隱式內存泄漏,就是程序自動給咱們的變量分配了內存空間,可是直到程序結束,他們才被釋放。雖然程序結束,釋放了全部內存,可是若是長時期的不結束這段程序,內存也就不能及時釋放。
高級前端進階公衆號文章閱讀筆記
目錄:
一、意外的全局變量
二、被遺忘的定時器或回調函數
三、脫離DOM的引用
四、閉包
一、意外的全局變量
在函數做用域中,未使用var定義的變量會在全局建立一個新的全局變量:
function foo(){ a = 2;
var b = '局部變量' }
此時,a沒有使用var定義,就會在全局對象中建立一個a屬性。
或者,在普通函數中使用this時,也就在全局對象window上建立一個屬性:
function bar(){
this.a = 2;
}
這種作法和上邊定義那個a沒什麼區別(固然,在不使用new bar構造函數的狀況下),this指向window(非嚴格模式下);
由於若是使用嚴格模式,this指向的是undefiend;
解決方法就是:
儘可能變量都定義在局部函數做用域中,而且記得使用var等變量聲明一下。
另外,若是不是使用構造函數,普通函數內部也記得使用this的時候,js的文件頭部加上"use strict"字樣,表示使用嚴格模式編譯。
若是必須使用全局變量,那麼要確保使用完之後將該變量指向null或重定義。
定時器setInterval
var a = fun();
setInterval(function(){
var node = document.getElementById('node');
if(node){
node.innerHTML = 'test';
}
},1000);
上例:節點node或數據不須要時,定時器setInterval依然指向這些數據,因此即便node節點被溢出,interval仍舊存活而且垃圾回收期無法回收。
解決是終止定時器。
這種循環定時器,是必定要設置關閉條件,而後將其clear而且將timer指向null
三、脫離DOM的引用:
若是把DOM存成字典(鍵值對)或者數組,此時,一樣的dom元素存在兩個引用:
一個在DOM樹中,另外一個在字典中,那麼未來須要把兩個引用都清除。
好比:
var elements ={
btn: document.getElementById('button'),
img: document.getElementById('image'),
};
function doS(){
elements.img.src = 'test/img.png';
elements.btn.click();
}
function removeBtn(){
document.body.removeChild(document.getElementById('button'));
}
//此時,仍舊存在一個全局的#button的引用,
//elements字典,btn元素仍舊在內存中,不能被回收
若是代碼中保存了表格某一個<td>的引用,未來決定刪除整個表格的時候,,你會認爲回收器會回收除了已保存的<td>之外的其餘節點?
事實上:<td>是表格的子節點,子元素和父元素是引用關係,因爲代碼保留了<td>的應用
致使整個表格仍待在內存中,因此保存DOM元素引用的時候要當心謹慎。
四、閉包
閉包的關鍵是匿名函數能夠訪問父級做用域的變量。
咱們知道,函數在調用完畢以後,會被拋出執行棧進行銷燬,且函數內部的局部變量也就不存在的。
可是若是有閉包的存在,函數被拋出執行棧之後,因爲閉包內部引用了父級函數做用域內部的局部變量,
這些變量就不會被銷燬,而是繼續佔據着內存空間,嚴重時形成泄漏。這是閉包的特性,但也是他的缺點。
一樣的,能夠不用的時候指向null
(高程3)一旦肯定數據再也不使用,能夠手動將其值設置爲null來釋放其引用。 —— 解除引用。
此作法適用於全局變量和全局對象的屬性。由於局部變量大多會在他們離開執行環境時自動被解除引用。
其餘的對照第六點中的對應狀況尋找對應解決方法吧。
引用閱讀:
【1】《阮一峯的網絡日誌》 博客相關文章
【2】《高級前端進階》 公衆號相關文章
【3】《垃圾回收的標記清除算法詳解》 博文
【4】《javascript中的內存管理和垃圾回收》小火柴的藍色理想
【5】《內存泄漏》搜狗百科
【6】《內存機制相關面試題》
【n】其餘多個,,,若有雷同,那就是了。。。