数学中的闭包

数学上说的闭包,说的是对集合(比如实数集合)中的成员进行某一操作(比如加法)时,如果得到的结果总是同一个集合中的某一成员,那么这个集合对这个操作来说就是一个闭包。
1665908837495 1d813ae6 24dc 4d26 b207 55090ecde59b

编程里的闭包

闭包就是一个特殊的高阶函数,它返回的函数里,存在对外层函数的变量的引用,导致这些变量,永远不会被垃圾回收。至于为什么叫闭包,不用 太过纠结,也不要和数学里的“闭包”产生联系,因为如果从数学“闭包”意义来看编程世界里的闭包,那么编程世界里的“闭包”应该叫“开包”才对,因为上述的导致不会被垃圾回收的变量,其实是在包的范围里打了一个洞!参见上图,编程世界里的“闭包”其实对应了右半部分(数学中的开包)。

https://mp.weixin.qq.com/s?__biz=MzAxNTk3ODgxNA==∣=2247483701&idx=1&sn=c3e2e1189e42f9d53e34506393124ad9&chksm=9bfa9964ac8d1072468a1b71e8c5e0970a3a2f539aaee225dfbdc217674fa38aa79fb6cfd86b&scene=21#wechat_redirect

用途

https://zhuanlan.zhihu.com/p/350078310
由于它是高阶函数,执行一次后返回的,仍然是一个函数。这样就延迟了最终函数的执行。这种延迟最终函数执行的特性,可以衍生出多种用途,举几个例子:

智能数据对象

Promise 和 Memoize 其实已经很常见了,但是“智能数据对象”,则相对少见。我感觉就是见过,但具体想不起来真正使用到的地方了。今天是在看《计算机程序的构造与解释》一书时,了解到了智能数据对象这一个概念,本质上也是一个高阶函数,并且就是一个闭包。

https://sicp.jiwai.win/zh_cn/2.-%E6%9E%84%E9%80%A0%E6%95%B0%E6%8D%AE%E6%8A%BD%E8%B1%A1/2.75.html

在面向对象编程的世界里呆久了,似乎构造函数一定需要构造出一个“坚固”的对象,即有明确属性的对象。比如一个复数类,一定会想着用一个属性去存储其实部,用另一个属性去存储其虚部。就算再添加几个方式,用来分别获取模和幅角,也一定是用 this.x 和 this.y 来进行计算。

即大致是这样的代码:

https://runkit.com/jeff-tian/class-version-of-complex-numbers

javascript class ComplexNumber { constructor(x, y) { this.x = x; this.y = y;

this.mag = Math.sqrt(this.x * this.x + this.y * this.y);
this.ang = Math.atan(y, x);

}

static fromMagAng(mag, ang) { const x = mag * Math.cos(ang); const y = mag * Math.sin(ang);

const c = new ComplexNumber(x, y);
c.mag = mag;
c.ang = ang;

return c;

}

get realPart() { return this.x; }

get imagPart() { return this.y; }

get magnitude() { return this.mag; }

get angle() { return this.ang; } }

const c = new ComplexNumber(1, 2); console.log(c.realPart); console.log(c.imagPart); console.log(c.magnitude); console.log(c.angle);

说它是“坚固”的对象,因为有明确的属性,不存在中间状态(未执行的函数是某间中间状态)。然而,通过闭包,可以不用在对象里存储具体的属性值,从而变成所谓的“智能数据对象”。虽然没有在一个“对象”中存储具体的值,但通过提供相应的方法,实现的效果也是一样的。

https://runkit.com/jeff-tian/closure-version-of-complex-numbers

javascript const makeComplexNumberFromRealImag = (x, y) => { const dispatch = (op) => { switch (op) { case realPart: return x; case imagPart: return y; case magnitude: return Math.sqrt(x * x + y * y); case angle: return Math.atan(y / x); default: throw new Error(Unknown op: + op); } }

return dispatch; };

const n = makeComplexNumberFromRealImag(1, 2); console.log(n(realPart)); console.log(n(imagPart)); console.log(n(magnitude)); console.log(n(angle));

注意以上的 n,它是一个函数,它根据输入(指令)返回最终的数(或者对于不认识的指令抛出错误),这就是所谓的“智能”。

当然,在类的写法里,还有一种基于模和辐角来构造出复数的 fromMagAng静态方法,使用闭包的方式也可以实现一个上面的“智能数据对象”:

https://runkit.com/jeff-tian/make-complex-number-from-mag-ang

javascript const makeComplexNumberFromMagAng = (mag, ang) => { const dispatch = (op) => { switch (op) { case realPart: return mag * Math.cos(ang); case imagPart: return mag * Math.sin(ang); case magnitude: return mag; case angle: return ang; default: throw new Error(Unknown op: + op); } }

return dispatch; };

const z = makeComplexNumberFromMagAng(Math.sqrt(5), Math.atan(2)); console.log(z(realPart)); console.log(z(imagPart)); console.log(z(magnitude)); console.log(z(angle));

总结

为什么面试总被问到闭包?因为它的玩法实在太多了。