闭包是 JavaScript 中的一种编程模式,它允许在嵌套的代码块中使用外层词法作用域里的变量。 JavaScript 对闭包的支持是透明的,人们常常在不知道闭包是什么的情况下就已经在用它了。
// Top-level declarations are global-scope
const dozen = 12;
{
// Braces create a new block-scope
// Referencing the outer variable is a closure.
const twoDozen = dozen * 2;
}
// Because of the block-scope declaration, twoDozen is not available here.
twoDozen;
// => Uncaught ReferenceError: twoDozen is not defined
除了大括号{}之外,函数(以及类)也会创建新的作用域,并且可以_封闭_值:
const dozen = 12;
// Functions create a new function-scope and block-scope.
// Referencing the outer variable here is a closure.
function nDozen(n) {
// This is declared inside the function scope, and uses the top-level scope.
// This works, and encloses the value 12.
const twoDozen = dozen * 2;
// This only uses the locally declared variable and the passed argument to the parameter `n`
return (twoDozen / 2) * n;
}
// Because of the function-scope declaration, twoDozen is not available here.
twoDozen;
// => Uncaught ReferenceError: twoDozen is not defined
正如twoDozen这几个例子所示,值可以被封闭在_嵌套_的作用域中(函数、代码块等),但无法从这个环境中取出。
在大多数情况下,现代 JavaScript 的本意就是让值不要_泄漏_到外层作用域。
使用可变的变量声明(例如let或var),可以保留一部分状态:
let counter = 0;
// This function closure increments the counter's state in the outer lexical context.
// This way the counter can be shared between many calling contexts.
export function increment() {
counter += 1;
return counter;
}
increment();
// => 1
counter;
// => 1
把这两种思路结合起来:封闭一个值来保存状态,而且被封闭的值不会泄漏到外部,这样就可以创建私有值了。
最常见的做法是定义一个函数,让它返回一个封闭了某些状态的函数。
export function makeCounter() {
let counter = 0;
// This returns a new function that encloses the local variable counter
return function increment() {
counter += 1;
return counter;
};
}
// Counter did not leak
counter;
// => Uncaught ReferenceError: counter is not defined
// This creates a new counter.
// This assigns the increment function to the variable myFirstCounter.
const myFirstCounter = makeCounter();
typeof myFirstCounter;
// => function
myFirstCounter.name;
// => increment
myFirstCounter();
// => 1
myFirstCounter();
// => 2
// This creates new counter (with new, separate local state / enclosed counter variable)
const mySecondCounter = makeCounter();
mySecondCounter();
// => 1
// It does not affect the first counter.
myFirstCounter();
// => 3
很多程序员觉得闭包是个很难的概念,而且从一个函数返回另一个函数,在所有编程语言中都不常见,甚至根本不可能。 如果你想找更多阅读材料,MDN 上关于闭包的指南相当全面。
你的设计公司一直主要使用 CSS 变换来构建网页。经过一番讨论,大家决定开始使用 JavaScript 动态执行一些计算。你的部分队友对 JavaScript 不太熟悉,所以你决定使用闭包,为{x, y}坐标对创建可复用的变换。
实现translate2d函数,它返回一个函数,这个函数利用闭包对坐标对执行可重复的二维平移。
在几何中,平移指的是把点、向量或图形沿同一方向移动相同的距离,也可以理解为给每个点加上一个常数。
const moveCoordinatesRight2Px = translate2d(2, 0);
const result = moveCoordinatesRight2Px(4, 8);
// result => [6, 8]
实现scale2d函数,它返回一个函数,这个函数利用闭包对坐标对执行可重复的二维缩放。
在几何中,均匀缩放指的是把向量或图形沿同一方向放大或缩小,也可以理解为把每个点乘以一个常数(缩放系数)。
本题假定缩放值都是正数。
const doubleScale = scale2d(2, 2);
const result = doubleScale(6, -3);
// result => [12, -6]
组合两个变换函数,以执行可重复的变换。这通常称为_函数复合_,其中第一个函数_'f(x)'的结果会作为第二个函数'g(x)'_的输入。
const moveCoordinatesRight2Px = translate2d(2, 0);
const doubleCoordinates = scale2d(2, 2);
const composedTransformations = composeTransform(
moveCoordinatesRight2Px,
doubleCoordinates,
);
const result = composedTransformations(0, 1);
// result => [4, 2]
实现memoizeTransform函数。它接收一个要_记忆化_的函数,然后返回一个新函数;这个新函数会记住传给所提供函数的输入,这样在再次用相同的实参调用时,最后的返回值就能被“记住”,并且只计算一次。
记忆化有时用于_动态规划_。它让开销大的运算只执行一次,因为结果会被记住。注意,本题只记住最后一次结果,这一点不同于动态规划中某些会记忆_所有_结果的解法。
const tripleScale = scale2d(3, 3);
const memoizedScale = memoizeTransform(tripleScale);
memoizedScale(4, 3); // => [12, 9], this is computed since it hasn't been computed before for the arguments
memoizedScale(4, 3); // => [12, 9], this is remembered, since it was computed already