面試官問我:什麼是JavaScript閉包,我該如何回答

weixin_34127717發表於2019-02-19

閉包,有人說它是一種設計理念,有人說所有的函式都是閉包。到底什麼是閉包?這個問題在面試是時候經常都會被問,很多小白一聽就懵逼了,不知道如何回答好。
這個問題也有很多朋友在公眾號給李老師留言了,問題表達方式不一樣,都是終歸到一點,就是對閉包沒有很清晰的理解。大家經常去網上找相關資料,但是對閉包的說法都是各種各樣的,讓大家對閉包的定義沒有一個概念。
所以今天我們來一起講講什麼是閉包,幫助大家理解,今天的內容可以直接收藏起來。方便以後看。

什麼是閉包(Closure)

簡單講,閉包就是指有權訪問另一個函式作用域中的變數的函式。
MDN 上面這麼說:閉包是一種特殊的物件。
它由兩部分構成:函式,以及建立該函式的環境。環境由閉包建立時在作用域中的任何區域性變數組成。

這種官方的概念是比較難理解的,在面試的時候說出來也不是很專業,因為沒辦法有個具體的邏輯。
我個人認為,理解閉包的關鍵在於:外部函式呼叫之後其變數物件本應該被銷燬,但閉包的存在使我們仍然可以訪問外部函式的變數物件,這就是閉包的重要概念。
10692

產生一個閉包

建立閉包最常見方式,就是在一個函式內部建立另一個函式。下面例子中的 closure 就是一個閉包:
10698
閉包的作用域鏈包含著它自己的作用域,以及包含它的函式的作用域和全域性作用域。

閉包的注意事項

.通常,函式的作用域及其所有變數都會在函式執行結束後被銷燬。但是,在建立了一個閉包以後,這個函式的作用域就會一直儲存到閉包不存在為止。
10747
從上述程式碼可以看到add5 和 add10 都是閉包。它們共享相同的函式定義,但是儲存了不同的環境。在 add5 的環境中,x 為 5。而在 add10 中,x 則為 10。最後通過 null 釋放了 add5 和 add10 對閉包的引用。
在javascript中,如果一個物件不再被引用,那麼這個物件就會被垃圾回收機制回收;
如果兩個物件互相引用,而不再被第3者所引用,那麼這兩個互相引用的物件也會被回收。

閉包只能取得包含函式中任何變數的最後一個值
10758
大家看一下上面這個程式碼,arr陣列中包含了10個匿名函式,每個匿名函式都能訪問外部函式的變數i,那麼i是多少呢?
當arrFunc執行完畢後,其作用域被銷燬,但它的變數物件仍儲存在記憶體中,得以被匿名訪問,這時i的值為10。
要想儲存在迴圈過程中每一個i的值,需要在匿名函式外部再套用一個匿名函式,在這個匿名函式中定義另一個變數並且立即執行來儲存i的值。
10763
這時最內部的匿名函式訪問的是num的值,所以陣列中10個匿名函式的返回值就是1-10。

閉包中的this物件

10772
在上面這段程式碼中,obj.getName()()實際上是在全域性作用域中呼叫了匿名函式,this指向了window。
這裡要理解函式名與函式功能是分割開的,不要認為函式在哪裡,其內部的this就指向哪裡。
window才是匿名函式功能執行的環境。
如果想使this指向外部函式的執行環境,可以這樣改寫:
10779
在閉包中,arguments與this也有相同的問題。下面的情況也要注意:
10782
obj.getName();這時getName()是在物件obj的環境中執行的,所以this指向obj。
(obj.getName = obj.getName)賦值語句返回的是等號右邊的值,在全域性作用域中返回,所以(obj.getName = obj.getName)();的this指向全域性。要把函式名和函式功能分割開來。

記憶體洩漏

閉包會引用包含函式的整個變數物件,如果閉包的作用域鏈中儲存著一個HTML元素,那麼就意味著該元素無法被銷燬。所以我們有必要在對這個元素操作完之後主動銷燬。
10791

函式內部的定時器

當函式內部的定時器引用了外部函式的變數物件時,該變數物件不會被銷燬。
10795

閉包的應用

應用閉包的主要場合是:設計私有的方法和變數。
任何在函式中定義的變數,都可以認為是私有變數,因為不能在函式外部訪問這些變數。私有變數包括函式的引數、區域性變數和函式內定義的其他函式。
把有權訪問私有變數的公有方法稱為特權方法(privileged method)。
10803
在這裡,我們需要理解兩個概念:
模組模式(The Module Pattern):為單例建立私有變數和方法。
單例(singleton):指的是隻有一個例項的物件。JavaScript 一般以物件字面量的方式來建立一個單例物件。
10809
上面是普通模式建立的單例,下面使用模組模式建立單例:
10813
匿名函式最大的用途是建立閉包,並且還可以構建名稱空間,以減少全域性變數的使用。從而使用閉包模組化程式碼,減少全域性變數的汙染。
10817
在這段程式碼中函式 addEvent 和 removeEvent 都是區域性變數,但我們可以通過全域性變數 objEvent 使用它,這就大大減少了全域性變數的使用,增強了網頁的安全性。

運用閉包的關鍵

閉包引用外部函式變數物件中的值;
在外部函式的外部呼叫閉包。
閉包的缺陷
閉包的缺點就是常駐記憶體會增大記憶體使用量,並且使用不當很容易造成記憶體洩露。
如果不是因為某些特殊任務而需要閉包,在沒有必要的情況下,在其它函式中建立函式是不明智的,因為閉包對指令碼效能具有負面影響,包括處理速度和記憶體消耗。
最後 來一道有關閉包的面試題
下面程式碼中,標記 ? 的地方輸出分別是什麼?
10829
大家結合今天講解的內容,思考一下答案,大家可以把答案發到留意上吧。

好了,今天的講解就那麼多,如果你還有什麼前端問題想提問的,或者你想李老師下次給大家講什麼內容,可以直接留意提問,說不定下次文章就會講解了。
如果你覺得這篇文章對你有幫助,請轉發點贊支援一下!

相關文章