关于回答这个问题,我们可以从四个方面入手:
- 什么是闭包
- 闭包解决了什么问题
- 闭包的应用场景
- 闭包的缺点
什么是闭包
一句话解释就是可以访问另一个函数作用域中变量的函数。
然后解释一下这句话,在JS的函数执行会形成一个作用域,执行完成作用域会销毁,但是闭包就不要一样了,由于闭包函数是函数内部的子函数,内部函数可以访问上级作用域,由于函数作用域中存在变量引用,所以执行完成不会立即销毁。
闭包解决了什么问题
- 可以读取函数内部的变量
- 让这些变量始终保持在内存中,函数调用后不会被清除。
let bar = function () {
let local = '局部变量'
return function () {
return local
}
}
var baz = bar()
console.log(baz())
console.log(baz())
一般而言执行bar后,局部变量就会随着作用域销毁,但是这里返回的是一个匿名函数,匿名函数可以访问上级作用域中的变量 local。bar的作用域发现变量有引用关系,就不会销毁了。虽然在外部作用域中无法直接访问local,但是若要访问,只要通过这个中间函数周转一下就可以访问。之后只要调用baz函数就可以获取local变量了,不管你执行多少次,问题在于一旦有变量引用这个中间函数,这个中间函数就不会释放,同时也会使原始的作用域得不到释放。
闭包的应用场景
通过闭包抑制变量,来保存变量的状态
function addCounter() {
let counter = 0
const myFunction = function () {
counter = counter + 1
return counter
}
return myFunction
}
const increment = addCounter()
const c1 = increment()
const c2 = increment()
const c3 = increment()
console.log('increment:', c1, c2, c3);
// increment: 1 2 3
在这段代码中increment实际上就是闭包函数myFunction, 它一共运行了三次,第一次的值是1,第二次的值是2,第三次的值是3。这证明了,函数addCounter中的局部变量counter一直保存在内存中,并没有在addCounter调用后被自动清除。通过抑制变量实现数字的累加。
常考的面试题还有f(1)(2)(3)实现1+2+3
// 实现方法1,用闭包抑制变量,保存参数,当参数达到一定的个数进行求和操作
const add = (function (length) {
let allArgs = [];
function _add (...args) {
allArgs = [...allArgs, ...args];
if (allArgs.length >= length) {
let res = allArgs.reduce((prev, cur) => prev + cur, 0);
allArgs.length = 0
return res;
} else {
return _add;
}
}
return _add
})(3)
闭包的缺点
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,我们建议读者只在绝对必要时再考虑使用闭包。虽然像V8等优化后的JavaScript引擎会尝试回收被闭包占用的内存,但请大家还是要慎重使用闭包。