ECMAScript 的數據有兩種類型:基本類型值和引用類型值,基本類型指的是簡單的數據段,引用類型指的是可能由多個值構成的對象。Undefined、Null、Boolean、Number 和 String 是值類型,其餘都是引用類型。前端
咱們建立的原始類型、對象、函數等等,都會佔用內存。爲了防止溢出,咱們就須要對不用的數據進行刪除。這就是垃圾回收。segmentfault
JavaScript 內存管理的關鍵概念是可觸及(Reachability)。
個人理解就是還處於被引用狀態。
將全局(不管是window仍是global)比做樹根,咱們建立的原始類型、對象、函數等等比做一個個枝杈。若是能夠從window不斷層的知道某個變量,那這個變量就是可觸及的,不可回收的。數組
舉個例子:閉包
// user has a reference to the object let user = { name: "John" };
箭頭表明的是對象引用。全局變量 "user"
引用了對象{name: "John"}
(簡稱此對象爲 John)。John 的 "name"
屬性儲存的是一個原始值,因此無其餘引用。函數
若是覆蓋 user
,對 John 的引用就丟失了:性能
user = null;
如今 John 變得不可觸及,垃圾回收機制會將其刪除並釋放內存。spa
js的內存空間分爲棧 (stack)、堆 (heap);其中棧存放變量,堆存放複雜對象。
借用一張圖直觀感覺一下3d
只能存放基本數據類型的數據和對象類型的引用地址也叫哈希碼。裏面的數據後進先出。
對棧內數據進行復制修改時:code
是用來存儲 「數組類型」 和「對象類」的數據。特色是存儲空間大。
對堆內數據進行復制修改時:對象
有了前面的鋪墊,咱們再來看看閉包是怎麼回事。仍是舉個例子:
//決策層開會決定生產新一代phone手機,就弄了個叫PhoneFactory的企劃案 let Proposal = function(){ //新一代手機信息被封裝在企劃案中 let version = "XX", money = 10000 return function (){ //生產新的手機 return { version: version, money: money } } } //執行者根據策劃案建成了一個工廠, 工廠方法每執行一次就產出一個手機 let Factory = new Proposal(); let phone1 = Factory(); console.log(phone1.version)
對於手機的version, money而言, 它是策劃書Proposal的內部變量,而Proposal的同級phone1應該是訪問不到。但實際上咱們仍是拿到了手機的版本和價格數據。這種反常的現象咱們就叫它Closure,中文名閉包。
咱們無論它爲何叫這個名字,先看看具體是什麼緣由產生的。
根據上面說的垃圾回收機制。函數Proposal在執行過以後(第16行)就沒有引用。那麼Proposal久應該被回收。裏面的全部內部變量也應該被回收了。
但實際上 Proposal返回了一個新的函數Factory。而這個Factory是要可以訪問到它生成時的同級以及父祖輩變量。而Proposal內部變量version, money就有了新的引用。於是阻止了被回收。就像Factory生成了一個新的泡泡把它能訪問到的做用域包裹了起來。這就是閉包造成的緣由了。
基於上面的js內存機制的知識,咱們能夠畫出下面這張圖:
圖簡陋了點。。。。
但也能夠看出,對於變量version,money而言。雖然他們自己在proposal的黃色做用域中。但也在fatory生成的時候也被包含在了fatory打的可訪問的做用域氣泡內。不只他們,甚至更外層的也都被包含在內。
在proposal這個泡泡破碎以後,只有當打的紅色泡泡也破了,這些變量纔會真的被回收。這也是爲何閉包用多了會影響性能的緣由。