_闭包_是 JavaScript 里的一种编程模式,它让外层词法作用域中的变量可以在嵌套的代码块内使用。JavaScript 透明地支持闭包,人们常常在并不知道它是什么的情况下就用上了它。
// Top-level declarations are global-scope
const dozen = 12;
// Functions create a new function-scope and block-scope.
// Referencing the outer variable here is a closure.
function nDozen(n) {
return dozen * n;
}
_闭包_这个名字在历史上源自λ 演算,由 scheme 推广开来(来源),用来指代函数开放的和封闭的变量绑定。
数据隐私 / 数据封装
// Consider this function:
const saveNumber = (number) => {
// The saved value is unaccessible by the outer lexical scope.
const value = number;
// We can provide access to the primitive value with a function, but the original will never change
return () => value;
};
// Attempting to set the variable outside of its lexical scope results in an error
value = 42;
部分应用
// The arguments are applied one-by-one, using closures to arrive at the final result
function partialBuildUri(scheme) {
return function (domain) {
return function (path) {
return `${scheme}://${domain}/${path}`;
};
};
}
// A function could apply them all at once.
function buildUri(scheme, domain, path) {
return partialBuildUri(scheme)(domain)(path);
}
// Or apply a few to create a flexible system of functions for reuse.
function buildHttpsExercismUri(path) {
return partialBuildUri('https')('exercism.org');
}
在任何编程语言中,闭包都和变量作用域密切相关。在 JavaScript 中,变量的词法作用域有 3 种:
var关键字在函数内部声明的变量。let和const关键字在代码块({...})内部声明的变量。在实践中,闭包常常出现在函数(或代码块)使用了外层作用域里的变量时。在 JavaScript 中,这一点是透明支持的,所以人们经常在不特意提及的情况下就用到了闭包。
var关键字定义的是函数作用域变量。也就是说,用var定义的变量,在它所在的整个函数内任何地方都能访问,在任何嵌套的词法作用域(无论是函数还是代码块)中也能访问。
{
var five = 5;
}
// This will succeed because 'five' exists in the same scope
var tenTimes = five * 10;
let和const关键字定义的是块级作用域变量。let定义的是可变变量,它的值可以被多次赋值,也可以只声明而不初始化。const定义的变量在定义时必须初始化,之后它的值就不能再改变。如果const变量被赋为原始值,就不能再修改;如果被赋为引用值,比如一个 数组 或 对象,那么引用不能改变,但数组的内容可以修改。
看看这个代码块:
// 'two' is defined at the top-level, therefore available to the global-scope
const two = 2;
{
// This succeeds, as 'two' is available from the outer lexical scope
const oneLess = two - 1;
}
// This throws an error because oneLess is not defined outside of the previous inner lexical scope
console.log(oneLess);
虽然我们可以用大括号({...})随意创建代码块,但最常见的情况是它们包在 if语句、_函数_和_类_的周围。
const PI = 3.14;
// This function uses a closure for the value of PI (π)
function areaOfCircle(radius) {
return PI * radius * radius;
}