一.什么是闭包
MDN中对于闭包的描述:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包。懂了吧?不懂就对了,官方就是喜欢搞些极其正确标准的解释。大家都知道,在函数作用域外部是无法访问内部的对象或者变量的,但是闭包就可以,他就很牛,那是如何实现的呢?带着这个疑问我们来理解什么是闭包。我们看看下面这个例子:
function Fun(){
const num = 666
function fn(){
console.log(num);
}
return fn
}
const res = Fun()
res()
//------result:666
是不是可以很清晰的看到在函数Fun的外部访问了变量num?没错,这就是闭包的特性之一:可以在函数外部通过函数访问函数内部的变量。看到这有的同学就不服气了,js明明规定了在函数执行完之后它内部的词法环境(创建的所有变量)应该被销毁了啊,这里为什么还可以访问到num呢? 没错,js是这样规定的,但是我们仔细看会发下其实最后一行代码执行的不就是Fun()里面的fn函数嘛,也就是说函数fn仍然被res变量所引用,而fn执行所依赖的数据就是num,那你说咱都还没用完为啥要给我回收了呢?
二.闭包的特性
1 变量私有化
其实我们上面已经证明过了变量私有化的特性了(顺便提一句,私有变量就是那些函数可以调用但是外部无法直接获取的变量。),这里就不再赘述了。
2 持久化存储(用不好会造成内存泄漏的严重后果)
这个特性其实在收买了也提到过了,这里再给大家详细的说一下。认真看过第一节的小伙伴应该都知道js的垃圾回收机制无法回收函数Fun()里的变量了,这就达到了持久化存储的目的,那内存泄露一说呢?大家看下面这段代码:
function Fun(){
let num = 666
return function(){
return num
}
}
let res1 = Fun()
let res2 = Fun()
console.log(res1());//666
console.log(res2());//666
浏览器在读取到函数时,并非直接将函数体中代码执行,而是会将函数以字符串的形式存储在浏览器的内存中,这个过程浏览器会分配一块堆内存用于函数的存储,并记录函数存储的位置.也就是说res1和res2保存的位置在堆中是两块独立的内存,这样一来就有两块内存被长期占用而不被释放了,那十块,二十块....这里就不用我多说了吧,大家应该都懂了。
三.闭包的使用场景
1 防抖
光会理论不行啊,要在实际中使用才是真正理解了闭包。大家平时都写过防抖吧,我们来看一下不使用闭包的防抖函数:
function deb(){
if(timer) clearTimeout(timer)
timer = setTimeout(()=>{
fn()
},1000)
}
function fn(){
//xxx
}
大家可以看到我们的timer是在全局作用域下声明的,在实际开发中有很多地方都会使用防抖,也就是说每个防抖使用的地方都要重新创建一个timer,是不是造成了全局变量的污染?而我们的闭包是不是有个变量私有化的特性?这不就ok了吗?那我们来看看使用闭包的防抖:
function debounce(){
let timer = null
return function(){
if(timer) clearTimeout(timer)
timer = setTimeout(()=>{
fn()
},1000)
}
}
function fn(){
//xx
}
const deb = debounce()
这样写timer就成为了防抖的私有变量,那要用防抖直接调用封装好的防抖函数不就好了(说出现内存泄漏的小伙伴别抬杠哈qaq)。
2 模块化
模块化直接上例子,下面这个例子很经典同是也非常易懂
function Fun(){
let num = 0
function changeNum(val){
num += val
}
return {
increment(){
changeNum(1)
},
decrement(){
changeNum(-1)
},
result(){
return num
}
}
}
const res1 = Fun()
const res2 = Fun()
res1.increment()
res2.decrement()
console.log(res1.result());// 1
console.log(res2.result());// -1
我们可以把res1理解成模块1,res2理解成模块2,每个模块都有自己独立的数据独立的内存,互不影响,这也是利用了闭包的变量私有化的特性,所以我们看到的打印结果并不是0。
3 柯里化
函数柯里化的核心在于:函数里面返回函数,从而做到参数复用的目的,从函数里面返回函数大家就知道和闭包脱不了干系了是吧。因为我们这个章节主要讲解的是闭包,所以就不去抠柯里化了,给大家提一下,然后大家可以去看看专门讲解柯里化的文章,还是很多的,也写得很好。
四.总结
1 闭包的形成条件
(1)函数嵌套
(2)内嵌函数必须引用外部函数中的变量且内嵌函数必须被return出去
2 闭包的特性
(1)变量私有化
(2)持久性存储
3 闭包的使用场景
(1)防抖
(2)模块化
(3)柯里化
最后
要是文章中又欠妥的地方烦请各位在评论区指出或者私信我,谢谢*