跳至主要內容

闭包 (Closure)

LincZero大约 4 分钟

闭包 (Closure)

这篇笔记并不是某个指定语言的 “闭包”,而是从更广义更宏观的角度上,从设计的角度上来说

通用解释

闭包 (Closure)

概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域

简单理解:闭包 = 内层函数 + 引用的外层函数变量

通常会再使用一个函数包裹住闭包结构,以起到对变量的保护的作用

JavaScript

参考:

介绍

  • 特点
    1. 函数嵌套函数
    2. 内层函数可以访问外层函数的变量和参数
  • 作用
    1. 防止变量和参数被垃圾回收机制回收(变量持久化)
    2. 防止变量和参数被外部污染(变量只在闭包内部可访问)
  • 风险
    1. 滥用可能会造成内存泄露

代码例子

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()

这也是 "封装私有变量" 的用法

实现模块化

  • 代码:略

缓存函数

  • 代码:略

封装私有变量

  • 代码:略

实现函数柯里化

  • 代码:略

防抖和节流

  • 代码:略

补充

参考:【前端八股文】JavaScript闭包怎么理解呢open in new window

image-20240113204449536

(这个图片可以通过浏览器断点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变量。

借助垃圾回收机制的标记清除法可以看出:

  1. result 是一个全局变量,代码执行完不会立即销毁
  2. result 使用fn函数
  3. fn 用到 fun函数
  4. fun 函数里面用到 count
  5. count 被引用就不会被回收,所以一直存在

此时:闭包引起了内存泄露

注意:

  1. 不是所有内存泄露都需要手动回收
  2. 比如react里面很多闭包都不能回收

一个有意思的评论区补充

从广义上来说任何函数都有可能是闭包,从狭义上来说当函数引用了外部变量,就会形成闭包;闭包的作用延长了变量的生命周期,

因为GC会定期回收没有引用指向的变量。如果函数引用了某一个外部变量,GC在回收时会发现这个变量被引用着就不会回收掉 (反之会被回收),从而延长了变量的生命周期。

但是这样也会带来一个问题就是内存泄漏,所谓内存泄露就是有一些变量,我们只使用了一次,本该被释放掉腾出内存,但是没有被释放,内存资源被占用。这也就是为什么说使用闭包有可能造成内存泄露,如果只使用一次,就是内存泄漏,如果这个变量还有其他函数使用或被多次使用就不叫内存泄漏。为了防止内存泄露可以手动将变量清零null

Rust

https://www.bilibili.com/video/BV1d64y1K7M3

概念

  • 匿名函数
  • 可以保存到变量、可以作为参数传递
  • 可获取调用者作用域(环境)中的值
  • 参数和返回类型可以自动推断
// 函数
// 调用

Java