闭包 (Closure)
大约 4 分钟
闭包 (Closure)
这篇笔记并不是某个指定语言的 “闭包”,而是从更广义更宏观的角度上,从设计的角度上来说
通用解释
闭包 (Closure)
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
简单理解:闭包 = 内层函数 + 引用的外层函数变量
通常会再使用一个函数包裹住闭包结构,以起到对变量的保护的作用
JavaScript
参考:
- https://www.bilibili.com/video/BV1gM4y1y7bt
- 【前端八股文】JavaScript闭包怎么理解呢
介绍
- 特点
- 函数嵌套函数
- 内层函数可以访问外层函数的变量和参数
- 作用
- 防止变量和参数被垃圾回收机制回收(变量持久化)
- 防止变量和参数被外部污染(变量只在闭包内部可访问)
- 风险
- 滥用可能会造成内存泄露
代码例子
function makeCounter() {
let count = 0;
return function () {
count++;
console.log(count);
}
}
const counter = makeCounter();
counter(); // 输出1
counter(); // 输出2
counter(); // 输出3
用途
很多常见的库或框架都有用这种方式,如 Vue3、React等
(这里的代码省略一下,这篇笔记的重点不在这,有兴趣的可以看原视频)
闭包演变,为什么需要闭包
// 普通形式。存在问题:i是全局变量,容易被外界篡改
let i = 0
fucntion fn () {
i++
console.log(`函数被调用了${i}次`)
}
// 闭包形式。可以看作i是这个函数的私有变量,无法被外界随意修改
function count() {
let i = 0
function fn() {
i++
console.log(`函数被调用了${i}次`)
}
return fn
}
const fun = count()
这也是 "封装私有变量" 的用法
实现模块化
- 代码:略
缓存函数
- 代码:略
封装私有变量
- 代码:略
实现函数柯里化
- 代码:略
防抖和节流
- 代码:略
补充
(这个图片可以通过浏览器断点Sources来看到)
闭包的两个注意点:
- 一定有return吗?不是
- 一定有内存泄露吗?不是
return
外部如果想要使用闭包的变量,则需要return
// 无return
function outer() {
const a = 1
function f() {
console.log(a)
}
f()
}
outer()
// 有return
function outer() {
const a = 1
return function () {
console.log(a)
}
}
const fn = outer()
fn()
内存泄露
以下面的代码为例
function fn() {
let count = 1
function fun() {
count++
console.log(`函数被调用了${count}次`)
}
return fnn
}
const result = fn()
result() // 2
result() // 3
哪个变量可能引起内存泄露?Count变量。
借助垃圾回收机制的标记清除法可以看出:
- result 是一个全局变量,代码执行完不会立即销毁
- result 使用fn函数
- fn 用到 fun函数
- fun 函数里面用到 count
- count 被引用就不会被回收,所以一直存在
此时:闭包引起了内存泄露
注意:
- 不是所有内存泄露都需要手动回收
- 比如react里面很多闭包都不能回收
一个有意思的评论区补充
从广义上来说任何函数都有可能是闭包,从狭义上来说当函数引用了外部变量,就会形成闭包;闭包的作用延长了变量的生命周期,
因为GC会定期回收没有引用指向的变量。如果函数引用了某一个外部变量,GC在回收时会发现这个变量被引用着就不会回收掉 (反之会被回收),从而延长了变量的生命周期。
但是这样也会带来一个问题就是内存泄漏,所谓内存泄露就是有一些变量,我们只使用了一次,本该被释放掉腾出内存,但是没有被释放,内存资源被占用。这也就是为什么说使用闭包有可能造成内存泄露,如果只使用一次,就是内存泄漏,如果这个变量还有其他函数使用或被多次使用就不叫内存泄漏。为了防止内存泄露可以手动将变量清零null
Rust
https://www.bilibili.com/video/BV1d64y1K7M3
概念
- 匿名函数
- 可以保存到变量、可以作为参数传递
- 可获取调用者作用域(环境)中的值
- 参数和返回类型可以自动推断
// 函数
// 调用