首頁  >  文章  >  web前端  >  js中閉包的概念

js中閉包的概念

hzc
hzc轉載
2020-06-19 09:11:372278瀏覽

閉包並不是 JavaScript 特有的,大部分高階語言都具有這項能力。

什麼是閉包?

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).
##這段是MDN 上對閉包的定義,理解為:一個函數及其周圍封閉詞法環境中的引用構成閉包。可能這句話還是不好理解,看看範例:

function createAction() {
    var message = "封闭环境内的变量";
    
    return function() {
        console.log(message);
    }
}

const showMessage = createAction();
showMessage();    // output: 封闭环境内的变量
這個範例是一個典型的閉包,有這麼幾點要注意:

  1. showMessagecreateAction 執行後從中傳回出來的一個函數
  2. createAction 內部是一個封閉的詞法環境,message 作為該封裝環境內的變量,在外面是絕不可能直接存取。
  3. showMessagecreateAction 外部執行,但執行時卻存取到其內部定義的局部變數 message(成功輸出)。這是因為showMessage 引用的函數(createAction 內部的匿名函數),在定義時,綁定了其所處詞法環境(createAction 內部)中的引用(message 等)。
  4. 綁定了內部語法環境的匿名函數被
  5. return 帶到了 createAction 封閉環境之外使用,這才能形成閉包。如果是在 createAction 內部調用,不算是閉包。
好了,我相信1, 2, 4 都好理解,但是要理解最重要的第3 點可能有點困難—— 困難之處在於,這不是程式設計師能決定的,而是由語言特性決定的。所以

不要認為是「你」創建了閉包,因為閉包是語言特性,你只是利用了這個特性

如果語言不支援閉包,類似上面的程式碼,執行

showMessage 時,就會找不到 message 變數。我特別想去找一個例子,但是很不幸,我所知道的高階語言,只要能在函數/方法內定義函數的,似乎都支援閉包。

把局部定義的函數「帶」出去

前面我們提到了可以透過

return 把局部定義的函數帶出去,除此之外有沒有別的辦法?

函數在這裡已經成為“貨”,和其他貨(變數)沒有區別。只要有辦法把變數帶出去,那就有辦法把函數帶出去。例如,使用一個「容器」物件:

function encase(aCase) {
    const dog = "狗狗";
    const cat = "猫猫";
    aCase.show = function () {
        console.log(dog, cat);
    };
}

const myCase = {};
encase(myCase);
myCase.show();      // output: 猫猫 狗狗
是不是受到了啟發,有沒有聯想到什麼?

模組和閉包

對了,就是 exports 和 module.exports。在CJS (CommonJS) 定義的模組中,就可以透過

exports.something 逐一帶貨,也可以透過module.exports = ... 打包帶貨,但不管怎麼樣,exports 就是帶貨的那一個,只是它有可能是原來安排的exports 也可能是被換成了自己人的exports

ESM (ECMAScript Module) 中使用了

importexport 語法,也只不過是換種方法帶貨出去而已,和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 都定義了一個新的函數。函數是新的,名字不重要 —— 你能叫小明,別人也能叫小明不是。

所以,總結一下:


閉包是由一個函數以及其定義時所在封閉環境內的各種資源(引用)構成,得到的每一個閉包都是獨一無二的,因為構成閉包的環境資源不同(不同的局部環境,定義了不同的局部變量,傳入了不同的參數等)。

閉包,這回搞明白了!


推薦教學:《

JS教學

以上是js中閉包的概念的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除