##知識小儲備
ECMAScript 的資料有兩種型別:基本型別值和引用型別值,基本型別指的是簡單的資料段,引用型別指的是可能由多個值構成的物件。
Undefined、Null、Boolean、Number 和 String 是值型別,其他都是引用型別。
##垃圾回收 我們建立的原始型別、物件、函式等等,都會佔用記憶體。為了防止溢位,我們就需要對不用的資料進行刪除。這就是垃圾回收。
###可觸及(Reachability) 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 變得不可觸及,垃圾回收機制會將其刪除並釋放記憶體。
##記憶體機制 js的記憶體空間分為棧 (stack)、堆 (heap);其中棧存放變數,堆存放複雜物件。 借用一張圖直觀感受一下
###棧記憶體 只能存放基本資料型別的資料和物件型別的引用地址也叫雜湊碼。裡面的資料後進先出。 對棧內資料進行復制修改時: https://segmentfault.com/img/bVben13?w=720&h=300
###堆記憶體 是用來儲存 “陣列型別” 和“物件類”的資料。特點是儲存空間大。 對堆內資料進行復制修改時: https://segmentfault.com/img/bVben17?w=700&h=714
##理解閉包 有了前面的鋪墊,我們再來看看閉包是怎麼回事。還是舉個例子:
//決策層開會決定生產新一代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記憶體機制的知識,我們可以畫出下面這張圖: https://segmentfault.com/img/bVben18?w=1738&h=1422
圖簡陋了點。。。。 但也可以看出,對於變數version,money而言。雖然他們本身在proposal的黃色作用域中。但也在fatory生成的時候也被包含在了fatory打的可訪問的作用域氣泡內。不僅他們,甚至更外層的也都被包含在內。 在proposal這個泡泡破碎之後,只有當打的紅色泡泡也破了,這些變數才會真的被回收。這也是為什麼閉包用多了會影響效能的原因。