什么是闭包?
对于这样一个函数:
function generate() {
const a = 1;
return function () {
// a这个变量不在当前作用域,于是它是一个*变量。
// 引用了*变量的函数称为闭包。
console.log(a);
};
}
generate作为”高阶函数“返回了一个新的函数,该函数引用了外部作用域中的变量a,于是该函数称为闭包函数。
闭包就是:引用了*变量的函数。
*变量:指在当前作用域引用但既没有定义在当前作用域也未定义在全局作用域,而是定义在外层的局部作用域中的变量。
所以在此明确几个关于闭包的要点:
- 闭包必然是定义在一个函数内部的,因为局部作用域只存于函数内。
- 局部作用域的变量无法直接从外部访问,只能通过作用域链访问,所以闭包是访问局部变量的桥梁。
- 闭包造成的直接结果是*变量不被垃圾回收,所以*变量可以缓存一些信息。
闭包的典型应用场景有两个:
- 设置私有变量(基于局部变量的不可访问性)
- 柯里化和偏函数(基于*变量的不回收性)
私有变量
私有变量就是那些函数可以调用但是外部无法直接获取的变量。
比如下面这个场景:
function User() {
// 这里的_password承担着私有变量的作用
let _password;
return class User {
constructor(username, password) {
this.username = username;
// 使用*变量保存密码
_password = password;
}
login() {
console.log(this.username, _password);
}
};
}
const user = new (User())("diana", "password");
user.login();
我们希望隐藏用户的密码,只允许内部访问,此时就可以使用*变量作为“私有变量”,返回一个闭包进行访问。
局部变量的外部不可访问性是关键,本质上这一切都来自于JS使用的词法作用域模型,变量只能从内向外单向查找。
柯里化
柯里化是一种操作技巧,在编程中指的是:
将接受n个参数的单个函数转换为接受单个参数的n个函数。
比如下面这个场景:
function info(country, province, city) {
console.log(country + "-" + province + "-" + city);
}
我们想要打印城市信息,对于某个省的城市我们需要重复的输入country,province这两个字段,此时我们可以对该函数进行“柯里化”改造:
function info(country) {
return function (province) {
return function (city) {
console.log(country + "-" + province + "-" + city);
};
};
}
const province = info("中国")("浙江省");
province("杭州市");
province("温州市");
我们将接受三个参数"country,province,city"的函数转换为三个只接受单个参数的函数,然后根据需要随时生成一个已经内置”部分参数“的函数。
这么做最大的好处是提高了代码的复用性,我们可以提前缓存一些参数,避免重复输入。
偏函数
偏函数也是一种操作技巧,在编程中指的是:
将接受n个参数的单个函数任意固定a个参数,返回接受剩余n-a个参数的函数。
偏函数和柯里化属于一类操作,区别在于:
柯里化强调函数只能接受“单参数”,有n个参数就要拆解为n个单参数函数;
偏函数更加“随意”,将接受n个参数的函数一分为二,任意固定一个或多个参数。
上面的例子如果使用偏函数:
function info(country) {
return function (province, city) {
console.log(country + "-" + province + "-" + city);
};
}
info("中国")("浙江省", "杭州市");
info("中国")("浙江省", "温州市");
柯里化和偏函数本质没什么区别,只是约束不同。能够实现这类操作的核心有两点:
- 在于“*变量”的不回收性,由于垃圾回收不去销毁闭包中引用的*变量,我们才能缓存部分参数;
- 在于JS使用静态的词法作用域,词法作用域的划分和查询根据”代码书写“的位置来决定。
其实私有变量也用到了这两个特性。