最簡單的方式和例子來解釋閉包是什么
前言
作為一名前端開發(fā)者,相信大家在面試中經(jīng)常會(huì)被問到這種類似的問題。作為進(jìn)入前端還不久的一名偽小學(xué)生,曾經(jīng)深受閉包的困擾。雖然在網(wǎng)上查閱過許多文章,但是總是感覺似是而非。直到有一天遇到了一個(gè)問題,然后通過使用閉包把它解決了。突然發(fā)現(xiàn)自己好像對閉包有了新的的理解,就想在這里給大家分享出來,同時(shí)也正式的開始自己的博客之路。當(dāng)然,內(nèi)容中如果有什么錯(cuò)誤之處,希望各位大佬們踴躍的指出來。
作用域
要想搞清楚閉包首先一定要知道作用域相關(guān)的知識。相信大家都知道,在es6之前,js中作用域只有兩種,一種是全局作用域,一種是函數(shù)作用域,但是es6之后,出現(xiàn)了let? const ,使得js中也有了塊級作用域的概念,本文就不在詳述es6的知識,有興趣的大家可以自己去找來看。作用域之間的關(guān)系在js當(dāng)中又是這樣的:子作用域可以訪問父作用域的變量,但是父作用域是不能訪問子作用域的變量的(實(shí)際上這就是js的閉包機(jī)制,后面會(huì)詳細(xì)講解這是為什么)。還有一點(diǎn)就是,子域訪問父域的變量時(shí),是一層一層的往上找的,首先從當(dāng)前作用域開始找,找到的話就不會(huì)再往上找了,就是優(yōu)先找最近的。如果找到全局作用域還是沒有找到,就拋出一個(gè)錯(cuò)誤。以下是一段代碼:

相信大家對test,test1函數(shù)的執(zhí)行結(jié)果沒有什么異議,但是可能有部分同學(xué)對test2函數(shù)的打印結(jié)果有些異議,覺得應(yīng)該是10,我在這里解釋一下:首先就是函數(shù)一旦被定義,它的作用域就會(huì)一直存在,不會(huì)被改變;其次就是一定要注意,函數(shù)的父級作用域是定義這個(gè)函數(shù)時(shí)所在的作用域,而非調(diào)用這個(gè)函數(shù)時(shí)所在的作用域 。所以func的父級作用域是定義它的作用域,也就是test2,自然打印結(jié)果就是13,搞明白了這一點(diǎn)我們就繼續(xù)往下走。
閉包是什么
閉包是js的一種機(jī)制,就我個(gè)人的理解用最簡單的語言描述出來就是跨作用域取值,并且變量會(huì)被保存下來,函數(shù)執(zhí)行完畢后該變量不會(huì)被垃圾處理機(jī)制清除??缱饔糜颍ㄔL問的變量取值不在當(dāng)前的作用域,其實(shí)就是訪問其他函數(shù)的內(nèi)部變量)在js中被允許的只有一種情況,那就是子作用域可以訪問父作用域的變量,所以說我個(gè)人理解就是凡是涉及到跨作用域取值(子訪問父)的函數(shù)都有可能會(huì)形成一個(gè)閉包。同學(xué)們可能會(huì)覺得有點(diǎn)懵,我們再把剛才的圖貼出來:

? ?所以說,由上圖可以看出只要是訪問了全局作用域下的變量的一級函數(shù)(這里一級函數(shù)指的是父作用域是全局作用域的函數(shù))都會(huì)形成一個(gè)閉包,因?yàn)樗坏缱饔糜蛉≈盗耍以L問的變量都會(huì)被保存起來不會(huì)被清除。但是通常我們會(huì)忽略這種情況,我們更多的是去注意test2這種情況(函數(shù)作為一個(gè)返回值)。為什么呢,因?yàn)樗雌饋硖硭鶓?yīng)當(dāng),太簡單了,我們從一開始學(xué)習(xí)就知道,全局作用域下的值,任何函數(shù)都可以訪問,所以說我們理所應(yīng)當(dāng)?shù)木秃雎粤怂欢麄兊脑矶际遣畈欢嗟摹_@里,我們來重點(diǎn)分析一下test2函數(shù):

我們知道func形成了一個(gè)閉包,來解釋一下為什么:1.它有沒有跨作用域取值,當(dāng)然有,a變量;2.跨作用域取值的a變量有沒有被保存下來,有,我們清楚地可以看到每次執(zhí)行函數(shù)a都會(huì)比以前大,很顯然是沒有被清除的;其實(shí)有些同學(xué)疑惑的地方不是這里,有可能會(huì)問a變量為什么會(huì)被保存下來,我們這里來嘗試分析一下:test2()其實(shí)也就是func函數(shù)被作為一個(gè)變量保存了下來,這時(shí)候js就會(huì)解析到這個(gè)函數(shù)里面有一個(gè)a是跨作用域取值的,當(dāng)這個(gè)函數(shù)執(zhí)行完畢之后如果把a(bǔ)清除的話,下次執(zhí)行的話就會(huì)報(bào)錯(cuò),因此,js為了避免報(bào)錯(cuò),a變量就不會(huì)被清除,這其實(shí)是js語言的一個(gè)特性。假如你還是有點(diǎn)懵的話,我建立去對比一下全局變量。想象一下,我們訪問了一個(gè)全局作用域下的函數(shù),這個(gè)函數(shù)訪問了某個(gè)全局變量,當(dāng)這個(gè)函數(shù)執(zhí)行完畢的時(shí)候,這個(gè)全局變量會(huì)不會(huì)被清除呢,很明顯的不會(huì),大家可以仔細(xì)體會(huì)一下其中的奧妙。其次我們看到test2()()不是一個(gè)閉包,它肯定不是啊,它首先連一個(gè)函數(shù)都不是,它只是一個(gè)執(zhí)行結(jié)果,我們根據(jù)閉包函數(shù)的特性很明顯的就判斷出來,所以說大家在使用這個(gè)特性的時(shí)候,千萬不要犯這種錯(cuò)誤。還有就是大家要注意一點(diǎn),形成閉包的函數(shù)里的變量不會(huì)被清除,如果閉包使用過多,可能就會(huì)造成內(nèi)存泄漏,在函數(shù)執(zhí)行完畢的時(shí)候,我們可以給函數(shù)一個(gè)null值,就可以清除變量。
閉包的使用場景
1.防抖,節(jié)流函數(shù)

2.setTimeout實(shí)現(xiàn)setInterval

大家可以仔細(xì)體會(huì)一下這兩個(gè)函數(shù)的異同點(diǎn),以及都實(shí)現(xiàn)了什么效果,可以幫助你進(jìn)一步的理解,謝謝大家的閱讀!