引言
閉包是 Javascript 一個重要的概念,也是 Javascript 難理解的一個特性。在實際開發中,有很多高級功能和第三方 API 都使用到閉包,所以,掌握閉包的概念和使用,是前端開發必不可少的技能。
什麼是閉包?
在 JS 中,我們可以把閉包理解爲一個可以訪問並持有外部函數變量和參數的內函數。
爲什麼這麼說?如下圖,我們在 outter 函數中定義內函數 inner,並把 inner 函數打印出來,從打印結果我們發現,在 inner 函數的 scopes 中,形成了一個閉包,通過這個閉包,賦予了內部函數訪問外部環境(outer函數)的變量的能力。
閉包有什麼用?
閉包的主要作用
- 變量私有化,避免污染全局環境
- 變量會存在內存中,不會被銷燬,可作爲緩存使用
- 封裝主要的業務邏輯,提高代碼的複用性,有利於擴展
下面我們通過普通函數和閉包實現業務的對比,來解釋閉包的作用(感受使用閉包的好處)
通過計數器理解閉包
假設我們需要在網頁中實現一個計算器,當點擊按鈕時,計數值就加1
- 普通函數實現:
let num = 0;
button1.onclick = function() {
count();
console.log(num);
}
function count() {
return num++;
}
雖然以上代碼實現了功能,但是存在 3 個弊端:
- 首先變量 num 定義在全局環境中,污染全局環境
- 其次,除了 count 函數,其他地方也能修改計數值 num,count 函數失去計數意義
- 假如頁面有兩個按鈕需要控制兩個不同的計數器,應該這麼辦?count 函數無法複用了,解決方法只能再重新定義多一計數值 num1 和 函數 count1,可見代碼複用性低。如下所示,
//不推薦的實現方法
var num = 0;
var num1 = 0;
button1.onclick = function() {
count()
}
button2.onclick = function() {
count1()
}
function count() {
num++;
console.log(num);
}
function count1() {
num1++;
console.log(num1);
}
- 閉包實現方法
前面鋪陳這麼多,就是爲了閉包的粉墨登場(褒義),請看閉包的實現方式:
button1.onclick = count()
button2.onclick = count()
function count() {
var num = 0;
return function() {
num++;
console.log(num)
}
}
還記得第一點講閉包的概念嗎,我們打印內函數時內函數會創建一個持有外部變量的閉包,所以,我們這裏兩個按鈕分別對應兩個不同的 count 函數返回的兩個內函數,相當於對應兩個獨立的閉包,也就擁有不同的 num 值,當我用button1 改變計數器1 並不會影響到 計數器2,相互獨立。
button1 => count() => inner1 => closure1: {num1 = 0}
button2 => count() => inner2 => closure2: {num2 = 0}
同時,這裏我們不僅讓 num 私有化,也讓兩個計數器共用一套邏輯代碼,實現代碼了代碼的複用。
其他
類似於計數器,像第三方工具在實現防抖 debounce 函數功能也使用了閉包對核心代碼進行封裝,同理也是利用閉包可以訪問並持有外部函數變量和參數特性,使得多個防抖的元素對應不同的閉包,訪問互相獨立的變量 timer