閉包並非 JavaScript 特有的,大部分高級語言都具備這一能力。程序員
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). 閉包
這段是 MDN 上對閉包的定義,理解爲:一個函數及其周圍封閉詞法環境中的引用構成閉包。可能這句話仍是很差理解,看看示例:ide
function createAction() { var message = "封閉環境內的變量"; return function() { console.log(message); } } const showMessage = createAction(); showMessage(); // output: 封閉環境內的變量
這個示例是一個典型的閉包,有這麼幾點須要注意:函數
showMessage
是 createAction
執行後從中返回出來的一個函數。createAction
內部是一個封閉的詞法環境,message
做爲該封裝環境內的變量,在外面是毫不可能直接訪問。showMessage
在 createAction
外部執行,但執行時卻訪問到其內部定義的局部變量 message
(成功輸出)。這是由於 showMessage
引用的函數(createAction
內部的匿名函數),在定義時,綁定了其所處詞法環境(createAction
內部)中的引用(message
等)。return
帶到了 createAction
封閉環境以外使用,這才能造成閉包。若是是在 createAction
內部調用,不算是閉包。好了,我相信 1, 2, 4 都好理解,可是要理解最重要的第 3 點可能有點困難 —— 困難之處在於,這不是程序員能決定的,而是由語言特性決定的。因此不要認爲是「你」建立了閉包,由於閉包是語言特性,你只是利用了這一特性。code
若是語言不支持閉包,相似上面的代碼,在執行 showMessage
時,就會找不到 message
變量。我特別想去找一個例子,可是很不幸,我所知道的高級語言,只要能在函數/方法內定義函數的,彷佛都支持閉包。對象
前面咱們提到了能夠經過 return
把局部定義的函數帶出去,除此以外有沒有別的辦法?ip
函數在這裏已經成爲「貨」,和其餘貨(變量)沒有區別。只要有辦法把變量帶出去,那就有辦法把函數帶出去。好比,使用一個「容器」對象:資源
function encase(aCase) { const dog = "狗狗"; const cat = "貓貓"; aCase.show = function () { console.log(dog, cat); }; } const myCase = {}; encase(myCase); myCase.show(); // output: 貓貓 狗狗
是否是受到了啓發,有沒有聯想到什麼?get
對了,就是 exports 和 module.exports。在 CJS (CommonJS) 定義的模塊中,就能夠經過 exports.something
逐一帶貨,也能夠經過 module.exports = ...
打包帶貨,但無論怎麼樣,exports
就是帶貨的那一個,只是它有多是原來安排的 exports
也多是被換成了本身人的 exports
。it
ESM (ECMAScript Module) 中使用了 import
和 export
語法,也只不過是換種方法帶貨出去而已,和 return
帶貨差很少,區別只在於 return
只能帶一個(除非打包),export
能夠帶一堆。
還要補充的是,不論是 CJS 仍是 ESM,模塊都是一個封裝環境,其中定義的東西只要不帶出去,外面是訪問不到的。這和網頁腳本默認的全局環境不一樣,要注意區別。
若是用代碼來表示,大概是定義模塊的時候覺得是這樣:
const var1 = "我是一個頂層變量吧"; function maybeATopFunction() { }
結果在運行環境中,它實際上是這樣的(注意:僅示意):
// module factory function createModule_18abk2(exports, module) { const var1 = "我是一個頂層變量吧"; function maybeATopFunction() { } } // ... 遙遠的生產線上,有這樣的示意代碼 const module = { exports: {} }; const m18abk2 = createModule_18abk2(module) ?? module; // 想明白 createModule_18abk2 爲何會有一個隨機後綴沒?
扯遠了,拉回來。思考一個問題:理論上來講,函數是一個靜態代碼塊,那麼屢次調用外層函數返回出來的閉包函數,是同一個嗎?
試試:
function create() { function closure() { } return closure; } const a = create(); const b = create(); console.log(a === b); // false
若是以爲意外,那把 closure()
換種方式定義看會不會好理解一點:
function create() { closure = function() { } return closure; }
若是還不能理解,再看這個:
function create() { const a = function () { }; const b = function () { }; console.log(a === b); // false }
能理解了不:每一次 function
都定義了一個新的函數。函數是新的,名字不重要 —— 你能叫小明,別人也能叫小明不是。
因此,總結一下:
閉包是由一個函數以及其定義時所在封閉環境內的各類資源(引用)構成,拿到的每個閉包都是獨一無二的,由於構成閉包的環境資源不一樣(不一樣的局部環境,定義了不一樣的局部變量,傳入了不一樣的參數等)。
閉包,這回搞明白了!
喜歡此文,點個贊 ⇙
支持做者,賞個咖啡豆 ⇓