了解閉包之前我們需要懂的東西:需要知道JS的作用域鏈以及預解析。
進入話題,作用域鏈上面的東西是存在哪里的呢。 是一個棧結構,最底層是window對象。
棧就是類似一個桶裝結構,如圖1:
圖一 : 棧結構具有的特點就是先入后出。 好比你放進桶里的東西,你需要從最上面開始拿出來。
JS代碼的解析過程中:例如JS代碼里有一個函數foo.
<script>
var a = 1;
function foo()
{ }
foo();
</script>
預解析:那么預解析到這個函數時,發生了如下的事情: 由於函數是引用類型的對象,實際數據存在堆里,那么在棧里只存放了地址。0x1101這個地址指向堆的實際數據。
運行到這個函數時,它會創建一個foo的作用域鏈對象。然后存入一個棧里,這個棧底層永遠是window.
函數執行=>foo() 又會發生的事情:棧里面的foo創建了一個活動對象。即執行了這個函數。
當函數執行完畢:foo的作用域鏈出棧,沒有被指向的活動對象即銷毀。但是window不會被銷毀。
(PS:橡皮擦太小了,沒擦干凈)
閉包是什么呢? 官方解答是有權去訪問函數內的變量。
我們現在來寫一個函數:
function foo(){ var n = 0;
//返回了一個函數。 這個函數的n 是從foo里面找的n。 return function getNum() { n++; console.log(n);
return n; } } var fn = foo();
fn();
預解析就會多了一步:在執行foo的時候,也就是創建活動對象之后,會再執行 getNum的入棧步驟。getNum被調用時也會創建一個活動對象。但是最后並不會銷毀。
因為fn指向了這個閉環回路。
簡單的講:這個函數被保存在了內存之中,並且一直可以通過fn訪問到。
函數調用結束后:如果用fn指向這個getNum的作用域鏈對象。只要fn到getNum的指向沒有消失。那么就可以一直訪問到存儲在內存的n.
閉包的核心就是getNum的活動對象沒有被銷毀,即使fn指向不是這個getNum 這個東西封閉的環也不會注銷,會一直在內存中,這也是閉包的缺點,容易造成內存泄漏。
我們現在來梳理一下閉包的步驟:
1.函數內部嵌套子函數,
2.子函數得到父函數的值。
3.把內層函數返回出去。(但閉包的創建不止這個形式。還有其他的。)