閉包這個話題是每個前端開發(fā)者都無法避開的,也成為了面試中的常見問題,或許你能說出自己背下來的答案,但是很少有同學(xué)會能真正搞懂其中的原理。知其然,更要知其所以然,今天我就來帶領(lǐng)大家通過剖析閉包的代碼,讓大家徹底明白閉包這個概念
看下面一段經(jīng)典的閉包應(yīng)用代碼
那么 JavaScript 解析器是如何執(zhí)行這段代碼的呢?今天我們再一步一步解釋一下上面代碼的執(zhí)行過程。
1、行 1-8 在全局執(zhí)行上下文中定義了一個新的變量 count,被賦了一個函數(shù)定義。
2、行 9 在全局執(zhí)行上下文中定義了變量 increment。
3、行 9 執(zhí)行 count 函數(shù),并且將其返回值賦給變量 increment。
4、然后再次來到行 1-8,創(chuàng)建了一個本地執(zhí)行上下文。
5、行 2 在本地執(zhí)行上下文中定義了一個變量 now 賦值為 0。
6、行 3-6 到了核心步驟,在本地執(zhí)行上下文中定義一個變量 myFunc,變量內(nèi)容是一個新的函數(shù)定義,同時我們也會創(chuàng)建一個閉包,并且讓其成為函數(shù)定義的一部分,這個閉包包含了該函數(shù)所處的作用域內(nèi)的變量,也就是 now。我們可以這樣用下面這段偽代碼幫助理解
所以,只要 myFunc 這個函數(shù)(或者被賦給其它變量)存在,它的閉包就會一直跟著存在。
7、行 7 返回了 myFunc 變量的內(nèi)容,本地執(zhí)行上下文被銷毀,myFunc 和 now變量不復(fù)存在,但返回了 myFunc 的函數(shù)定義和它的閉包。
8、行 9 在全局執(zhí)行上下文中定義了 increment 變量,其值為 count 函數(shù)的執(zhí)行結(jié)果,于是 increment 變量現(xiàn)在包含了一個函數(shù)定義和閉包,它不再是 myFunc,但是在全局執(zhí)行上下文中,它有了一個新的身份 increment。
9、行 10 定義了一個新的變量 r1 暫時賦值為 undefined。
10、行 10 發(fā)現(xiàn)有 () 表示這是一個需要執(zhí)行的函數(shù),于是查找變量 increment,找到后執(zhí)行該函數(shù),它包含了 4-5 行返回的函數(shù)定義。
11、開始執(zhí)行 increment 函數(shù),創(chuàng)建一個本地執(zhí)行上下文。
12、行 4 我們需要去找一個變量 now,當(dāng)前函數(shù)中并不存在這個變量,然后在去全局執(zhí)行上下文中查找之前,我們會先檢查一下閉包,最后發(fā)現(xiàn)閉包中確實包含一個變量 now 值為 0。執(zhí)行完 now += 1 后,它的值變成了 1,然后又將這個值存回了閉包中的 now 變量中,于是現(xiàn)在閉包中 now 的值就變成 1 了。
13、行 5 我們返回了 now 的值 1 然后銷毀了本地執(zhí)行上下文。
14、回到行 10,返回值 1 被賦給了 r1 變量。
15、行 11-12 又重復(fù)了上面的執(zhí)行過程,閉包中的 now 也再次經(jīng)歷了被取出,被 +1,又被存回到閉包中的過程,最終變成了 3。
16、行 13,打印出 r1 r2 和 r3 的值,分別是 1 2 3。
每當(dāng)一個函數(shù)被聲明的時候,它就會包含一個函數(shù)定義和閉包,這個閉包包含該函數(shù)所處的上下文(也就是父級作用域)所有變量的集合。本質(zhì)上來說,閉包就是一個作用域的留存。
有同學(xué)要問了,是不是所有函數(shù)都有閉包呢,即使在全局下創(chuàng)建的函數(shù)?答案:是。但由于在全局下創(chuàng)建的函數(shù)本身就能訪問全局下的變量,所以閉包這個概念在這里就并沒有多大意義。然而對于這種函數(shù)返回函數(shù)的場景,由于被返回函數(shù)的父級也是一個函數(shù),所以更能突出閉包的作用,我們常說的閉包指的也就是這種場景。
其實我們也可以在 Chrome F12 中查看這個閉包,在 Console 中輸入下面的代碼回車
結(jié)果如下圖
可以看到,increment 有 3 個 Scope,其中就包含了 Closure 閉包,也可以明確看到里面的 now 變量。
感興趣的小伙伴,可以關(guān)注公眾號【grain先森】,回復(fù)關(guān)鍵詞 “vue”,獲取更多資料,更多關(guān)鍵詞玩法期待你的探索~