在了解var、let、const以及块作用域后,我们知道函数内部用let或const进行声明的变量在函数外部是无法进行访问和赋值的。但是在实际开发中我们又经常需要通过循环绑定事件,为了实现这一需求JS引入了闭包这一概念。
定义
闭包是由被封闭的函数和这个函数所处的作用域(词法环境)的引用组合而成。
简单来说就是在一个外函数内部创建一个里函数和多个块作用域变量,这个里函数和多个块作用域变量组成闭包,这个里函数就叫做闭包函数。
function out() {
let value = 'outA'
function inner(){
console.log(value)
}
return inner;
}
let funcA = out()
funcA()
作用
闭包函数可以用于保存自身所在的块作用域变量的最新状态,这里需要强调的是这里的块作用域变量指的是在外函数声明的变量。
例如一个外函数声明了一个变量value,里函数接受一个变量并对value进行操作。 如果实例化两个里函数,分别对其进行传参操作会发现两个实例会互相进行影响。
function out(){
let value = 0;
function inner(val) {
value += val
return value
}
return inner
}
let func = out()
let innerA = func(2)
let innerB = func(-1)
console.log(innerA)
console.log(innerB)
结果
2
1
如果不引入闭包概念,我们直接对函数进行两次实例化,就会发现原函数在实例化时不会保留块作用域变量的最新状态。对value的操作不会互相影响。
function func(val){
let value = 0;
return value += val;
}
let innerA = func(2)
let innerB = func(-1)
console.log(innerA)
console.log(innerB)
结果
2
-1
总结: 闭包可以帮助开发人员在开发过程中调用一个函数内部的块作用域变量,这个变量在实例化时传入的是引用,而不是进行变量数值的复制。所以闭包函数可以保存所在作用域变量的最新状态,而非每次实例化时将变量初始化为最新值。
常见用法
对函数内部块作用域变量进行操作
function counter(){
let count = 0;
return {
increment: function() {
count++;
return count;
},
decrement: function() {
count--;
return count;
}
}
}
let a = counter()
console.log(a.increment())
console.log(a.decrement())
结果
1
0
异步操作循环赋值
for (let i = 0; i < 5; i++) {
setTimeout(function(){
console.log(i)
}, i * 1000);
}
结果
0
1
2
3
4
函数工厂(柯里化)
function getName(name){
let firstName = name;
return function(secondName) {
return firstName + ' ' + secondName
}
}
let name = getName('Tom')
console.log(name('Martin'))
console.log(name('Honey'))
结果
> "Tom Martin"
> "Tom Honey"
注意事项
性能考虑
闭包会同时管理函数和函数所在的作用域,所以如果非必要或者是有性能消耗更低的选择时应该减少使用闭包。
例如在创建一个新的对象或类时,无需保留当前变量状态的方法应该绑定在原型上,而不是定义在构造函数内部。
变量回收
除非是特别大的变量,否则应当交由js垃圾回收策略进行处理。如果出现需要手动释放的情况,可以通过将实例化函数的变量赋值为null进行内存释放回收。