探索奧祕~閉包

weixin_33782386發表於2018-04-12

在我學習初期我對閉包的理解也不是很深,最近在學習了js更深入的知識後,對閉包有了更加深入的瞭解,下面我就跟大家詳細解讀一“閉包”!

一、首先什麼是閉包?

“官方”的解釋是:所謂“閉包”,指的是一個擁有許多變數和繫結了這些變數的環境的表示式(通常是一個函式),因而這些變數也是該表示式的一部分。
當一個函式能夠記住並訪問到其所在的詞法作用域及作用域鏈,特別強調是在其定義的作用域外進行的訪問,此時該函式和其上層執行上下文共同構成閉包。

相信很少有人能直接看懂這句話,因為他描述的太學術。我想用如何在Javascript中建立一個閉包來告訴你什麼是閉包,因為跳過閉包的建立過程直接理解閉包的定義是非常困難的。看下面這段程式碼:

function a(){
 var i=0;
 function b(){
 alert(++i);
 }
 return b;
}
var c = a();
c();

這段程式碼有兩個特點:
1、函式b巢狀在函式a內部;
2、函式a返回函式b。
這樣在執行完var c=a()後,變數c實際上是指向了函式b,再執行c()後就會彈出一個視窗顯示i的值(第一次為1)。這段程式碼其實就建立了一個閉包,為什麼?因為函式a外的變數c引用了函式a內的函式b,就是說:
當函式a的內部函式b被函式a外的一個變數引用的時候,就建立了一個閉包。

**需要明確的幾點:
1、閉包一定是函式物件(wintercn大大的閉包考證)
2、閉包和詞法作用域,作用域鏈,垃圾回收機制息息相關
3、當函式一定是在其定義的作用域外進行的訪問時,才產生閉包
4、閉包是由該函式和其上層執行上下文共同構成

二、接下來我們來看下閉包是如何產生的?
現在我假設JS引擎執行到這行程式碼
let baz = foo();
此時,JS的作用域氣泡是這樣的:


10828063-0579501f739e74fa.png
38412730-97a3fd6e-39bc-11e8-9a53-208d71ca98eb-660x572.png

這個時候foo函式已經執行完,JS的垃圾回收機制應該會自動將其標記為”離開環境”,等待回收機制下次執行,將其記憶體進行釋放(標記清除)。
但是,我們仔細看圖中粉色的箭頭,我們將bar的引用指向baz,正是這種引用賦值,阻止了垃圾回收機制將foo進行回收,從而導致bar的整條作用域鏈都被儲存下來。
接下來,baz()執行,bar進入執行棧,閉包(foo)形成,此時bar中依舊可以訪問到其父作用域氣泡中的變數a。

我們藉助chrome的除錯工具看下閉包產生的過程:
當JS引擎執行到這行程式碼let baz = foo();時:


10828063-39faaced03f800e2.jpg
38487982-9301fd1a-3c14-11e8-8238-57321d84eb05-660x439.jpg

圖中所示,let baz = foo();已經執行完,即將執行baz();,此時Call Stack中只有全域性上下文。
接下來baz();執行:


10828063-ab3832020f6b6cf3.jpg
38488086-e0d4349a-3c14-11e8-84af-ea05e7546a43-660x439.jpg

我們可以看到,此時bar進入Call Stack中,並且Closure(foo)形成。
三、閉包的應用場景
1、保護函式內的變數安全。以最開始的例子為例,函式a中i只有函式b才能訪問,而無法通過其他途徑訪問到,因此保護了i的安全性。

2、在記憶體中維持一個變數。依然如前例,由於閉包,函式a中i的一直存在於記憶體中,因此每次執行c(),都會給i自加1。
以上兩點是閉包最基本的應用場景,很多經典案例都源於此!

閉包的奧妙
閉包,它並不是很神祕,反而是在我們的程式中隨處可見,讓我們靜下心來,品味閉包的味道,走進“閉包”世界,感受它的魅力所在!

相關文章