引言
闭包是 Javascript 一个重要的概念,也是 Javascript 难理解的一个特性。在实际开发中,有很多高级功能和第三方 API 都使用到闭包,所以,掌握闭包的概念和使用,是前端开发必不可少的技能。
什么是闭包?
在 JS 中,我们可以把闭包理解为一个可以访问并持有外部函数变量和参数的内函数。
为什么这么说?如下图,我们在 outter 函数中定义内函数 inner,并把 inner 函数打印出来,从打印结果我们发现,在 inner 函数的 scopes 中,形成了一个闭包,通过这个闭包,赋予了内部函数访问外部环境(outer函数)的变量的能力。
闭包有什么用?
闭包的主要作用
- 变量私有化,避免污染全局环境
- 变量会存在内存中,不会被销毁,可作为缓存使用
- 封装主要的业务逻辑,提高代码的复用性,有利于扩展
下面我们通过普通函数和闭包实现业务的对比,来解释闭包的作用(感受使用闭包的好处)
通过计数器理解闭包
假设我们需要在网页中实现一个计算器,当点击按钮时,计数值就加1
- 普通函数实现:
let num = 0;
button1.onclick = function() {
count();
console.log(num);
}
function count() {
return num++;
}
虽然以上代码实现了功能,但是存在 3 个弊端:
- 首先变量 num 定义在全局环境中,污染全局环境
- 其次,除了 count 函数,其他地方也能修改计数值 num,count 函数失去计数意义
- 假如页面有两个按钮需要控制两个不同的计数器,应该这么办?count 函数无法复用了,解决方法只能再重新定义多一计数值 num1 和 函数 count1,可见代码复用性低。如下所示,
//不推荐的实现方法
var num = 0;
var num1 = 0;
button1.onclick = function() {
count()
}
button2.onclick = function() {
count1()
}
function count() {
num++;
console.log(num);
}
function count1() {
num1++;
console.log(num1);
}
- 闭包实现方法
前面铺陈这么多,就是为了闭包的粉墨登场(褒义),请看闭包的实现方式:
button1.onclick = count()
button2.onclick = count()
function count() {
var num = 0;
return function() {
num++;
console.log(num)
}
}
还记得第一点讲闭包的概念吗,我们打印内函数时内函数会创建一个持有外部变量的闭包,所以,我们这里两个按钮分别对应两个不同的 count 函数返回的两个内函数,相当于对应两个独立的闭包,也就拥有不同的 num 值,当我用button1 改变计数器1 并不会影响到 计数器2,相互独立。
button1 => count() => inner1 => closure1: {num1 = 0}
button2 => count() => inner2 => closure2: {num2 = 0}
同时,这里我们不仅让 num 私有化,也让两个计数器共用一套逻辑代码,实现代码了代码的复用。
其他
类似于计数器,像第三方工具在实现防抖 debounce 函数功能也使用了闭包对核心代码进行封装,同理也是利用闭包可以访问并持有外部函数变量和参数特性,使得多个防抖的元素对应不同的闭包,访问互相独立的变量 timer