前言:所有前端程序员最终都逃离不了闭包概念理解的命运啊...为什么会这样说当然是因为闭包是至关重要的的一个知识点。相信很多人接触到”闭包“这个词都是通过面试或者其它技术博客上了解到的。其实从这些种种事项都不难发现闭包的重要性。所以理解闭包概念和使用闭包将必然是我们的必修课(在理解闭包前,需要先去了解函数作用域的规则)!
一、什么是闭包?
至于什么是闭包,真的是众说纷纭啊,各有各的说法。但是不难发现他们都围绕着函数嵌套、局部作用域、内存泄漏、状态保存这些词语。其实在我理解(这里又多了个说法),常见的闭包就是违背js作用域的访问权限的一个‘合法’(这里为什么用合法来形容,因为闭包的确不是漏洞是被官方认可的)手段,而闭包的定义不是包含于此内,继续看下面例子。
二、闭包函数
1.1最常见的(抛出局部变量共外部使用,存在内存泄漏):
function fn(){
let count = 0;
return function(){
return ++count
}
}
const d = fn()
d() // 1
d() // 2
解释代码:上面这个就是最常见的闭包函数,从代码不难看出:定义了一个fn函数,内部有个变量 count,和一个return 标识 给fn函数定义了返回值,值是一个匿名函数。这个匿名函数又访问了fn 的局部变量count,把他自增比抛出去。
为什么这样做:首先变量 count 在外部是访问不到的,因为他是fn局部作用域的变量 只能在该作用域内(就是fn函数内,也包括内部的函数)才能访问到。
1、而通过return设置函数返回值的形式给fn函数设置返回值为一个匿名函数;
2、然后匿名函数又去范围fn的局部变量并返回。
3、然后用变量d接受fn()这个时候变量d就是fn返回的匿名函数。然后再调用的d()可以就可以访问到外部不能范围到的fn局部变量count。
绿色:匿名函数可以范围到 count,它返回了count。
蓝色:fn() 执行得到绿色(返回值为count的匿名函数),并赋值给d=(返回值为count的匿名函数)。
黄色:d()执行得到 count。
1.2:最简单的闭包函数
function closure() {
let count = 0;
function fn() {
console.log(count)
}
fn()
}
closure()
上面这个也是个闭包?没错,其实闭包函数很好写 就是两个函数嵌套,内部函数去范围外部函数定义的局部变量这样就形成了闭包。~~ 不信就来证明一下:
上面是告诉你断点位置的作用域是Closure 就是代表存在闭包,闭包内部有属性count,值是0。
继续来看看没有闭包的例子:
上面是告诉你断点位置的作用域是Script 就是代表不存在闭包,内部有属性count,值是0。
注:所以很多人说闭包是围绕在我们代码中,是特别常见的。就像你每天都能看见水喝水,但是你不知道它其实就是H₂O一样的道理。
三、深入了解闭包
1、闭包为什么会被很多人说会导致内存泄漏?其实不是必然的,操作不当才能导致内存泄漏。举个例子:
// 1、一个不会导致内存泄漏的闭包函数
function closure() {
let count = 2;
function fn() {
console.log(count)
}
fn()
}
closure()
2、一个可能会导致内存泄漏的闭包函数
function closure() {
let count = 2;
return function() {
return ++count
}
}
const data = closure()
data()
不难看出导致内存泄漏都是因为开发人员通过闭包的形式让外部作用域能访问到局部作用域中的数据。才会存在内存泄漏
1.1 内存泄漏:
举个例子:一杯水你装满了,就漏到地上了... 所以就被定义为内存泄漏~~ ps:我就在想为什么不叫内存溢出,照这个逻辑 还可以叫内存爆炸、内存流出、内存消失呢。。。
1.2 为什么会导致内存泄漏:
这里就要说到 js 内存回收机制了。因为浏览器内存是有限的,如果不进行回收点着点着浏览器就卡死了。js内存回收机制规定了,如果一个变量(引用类型)没有被其它地方引用了那么就会回收它占用的内存,也包括变量(引用类型)相互引用。所以,到这里就缩小范围了,那么很好确定闭包函数导致内存泄漏就是因为内存满了,内存满了又是因为变量(引用类型)没有被回收,变量(引用类型)没有被回收又是因为它被其它地方引用了~~所以闭包导致内存泄漏===闭包函数在内存泄漏之前一直都被引用着,回收不了,逐渐溢出(泄漏)!
function closure() {
let count = 2;
return function() {
return ++count
}
}
const data = closure() // 本该代码执行到这里 closure调用完后就会被回收。但是return的匿名函数被data引用了,所以closure一直在堆内存中不能被销毁。
data() // 3
data() // 4
上面执行data为什么值会累计叠加,就是因为closure一直存在着,所以count也一直存在。若要释放内存可以 data=null。
四、闭包的使用场景(列举):
- 递归:
function recursion(dataList) {
let list = []
function get(d) {
d.forEach(i => {
list.push(i)
if (i?.children?.length) get(i.children)
})
}
get(dataList)
return list
}
- 记录调用次数
function closure() {
let count = 0;
return function() {
console.log(++count)
}
}
const next = closure()
next()
next()
3. 防抖
function clickFn(){
console.log('点击')
}
function debounce(callBack) {
let time
return function (...props) {
clearTimeout(time)
let slef = this;
time = setTimeout(() => {
callBack.apply(slef, props)
}, 300)
}
}
const fn = debounce(clickFn)
欢迎指正...持续更新