前置知识:需要了解作用域
闭包的概念:
MDN:
闭包是由捆绑起来(封闭的)的函数和函数周围状态(词法环境)的引用组合而成。
提取这句话中两个关键词并用公式表示:
闭包 = 捆绑起来(封闭的)的函数 + 函数周围状态(词法环境)
简单点来说,根据黑马pink老师的话,就是
闭包 = 内层函数 + 外层函数的变量
再用图像来理解,或许能清晰一点
接下来用代码表示
function outer() {
const fruit = 'apple'
function inner() {
console.log(fruit)
}
inner()
}
outer()
当我们调用outer方法时,代码从上往下执行,先声明并赋值了一个变量fruit,接下来声明了内部方法inner,然后开始调用内部方法inner,从而在控制台打印出'apple'。
当然,这时候,不禁就要问了,如果我们只是为了打印出‘apple’,这样做岂不是多此一举?
没错,确实是多此一举,这样写只是为了方便理解闭包的概念,而闭包的主要作用并不在此。
闭包的应用:
先来看一个场景
页面中有一个按钮,每点击一次,就让控制台打印出点击的次数,代码如下所示:
<button>按钮</button>
<script>
let num = 0
function add() {
num++
console.log(`按钮被点击了${num}次`)
}
document.querySelector('button').addEventListener('click', add)
</script>
此时变量的num声明于全局作用域下,是一个全局变量,很容易被修改,我们希望这个num变量,只用来记录这个button,那么怎样才能让他更安全一点呢?我们就可以用到闭包,从而实现数据私有化!
在实现闭包前,我们先回想下闭包的公式,其中两个关键元素,内层函数和外层函数的变量,那么在这个方法中,谁是内层函数,谁是外层函数的变量呢?
内层函数就是我们真正要执行的逻辑
外层函数的变量就是执行这块逻辑要用到的变量
外层函数的作用就是把他们包起来
因此,内层函数就是add这个函数,而外层函数变量则是num这个变量
接下来,我们只差一个外层函数把他们包起来了,同时不要忘记把内层函数调用一次
然而,当我们打开控制台后, 事情并没有朝着我们希望的方向进行
无论点击了多少次,num仍然为1!
这是因为我们每次点击按钮时,outer被调用,然后重新定义num变量并将其初始化为0,接着在内部的add方法使得num加一。
那么怎样才能保留num的状态?
为了解决这个问题,我们需要使用return关键字,将内部函数add返回出去,再用一个新变量接受它,最后,每次点击时调用这个新变量。
这样说有点生硬,为了方便解释,我添加了一句log在初始化num后面
代码如下:
function outer() {
let num = 0
console.log('num', num)
return function() {
num++
console.log(`按钮被点击了${num}次`)
}
}
const returnOuter = outer() // 方法加上括号,表示调用
document.querySelector('button').addEventListener('click', returnOuter)
整个运行的流程为
当我们打开页面时,由于我们一开始就调用了outer(),所以控制台就会打印出‘num 0’,这也说明页面已经初始化了这个num变量,同时也返回了函数给returnOuter。
类似于:
接下来每点击一次,都会调用returnOuter这个方法,里面的num也不会被重置了
总结一下:
闭包是内层函数和外层函数变量的结合,可以实现数据的私有化(封闭数据,提供操作),同时外部也可以访问其内部的变量(正如 const returnOuter = outer())