还搞不懂闭包算我输(JS 示例)

闭包并非 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: 封闭环境内的变量

这个示例是一个典型的闭包,有这么几点须要注意:函数

  1. showMessagecreateAction 执行后从中返回出来的一个函数
  2. createAction 内部是一个封闭的词法环境,message 做为该封装环境内的变量,在外面是毫不可能直接访问。
  3. showMessagecreateAction 外部执行,但执行时却访问到其内部定义的局部变量 message(成功输出)。这是由于 showMessage 引用的函数(createAction 内部的匿名函数),在定义时,绑定了其所处词法环境(createAction 内部)中的引用(message 等)。
  4. 绑定了内部语法环境的匿名函数被 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 也多是被换成了本身人的 exportsit

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 都定义了一个新的函数。函数是新的,名字不重要 —— 你能叫小明,别人也能叫小明不是。

因此,总结一下:


闭包是由一个函数以及其定义时所在封闭环境内的各类资源(引用)构成,拿到的每个闭包都是独一无二的,由于构成闭包的环境资源不一样(不一样的局部环境,定义了不一样的局部变量,传入了不一样的参数等)。

闭包,这回搞明白了!


喜欢此文,点个赞 ⇙

支持做者,赏个咖啡豆 ⇓

相关文章
相关标签/搜索