Featured image of post JavaScript 的闭包(Closure)

JavaScript 的闭包(Closure)

闭包是什么?

闭包: 是指有权访问另一个函数作用域中的变量的函数。

闭包无处不在,比如 jQueryzepto 的核心代码都包含在一个大的闭包中。

先来看一个闭包 ⬇️ ⬇️ ⬇️

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const outSide = () => {
    let a = 1
    const inSide = () => {
        a ++
        console.log(a)
    }
    return inSide
}

const fn = outSide()

fn()  // 2
fn()  // 3
fn()  // 4

闭包的用途

闭包的实现原理,其实是利用了作用域链的特性。

我们都知道作用域链就是在当前执行环境下访问某个变量时,如果不存在就向外层寻找,最终寻找到最外层也就是全局作用域,这样就形成了一个链条。

隐藏变量,避免污染

在 Javascript 中,如果一个对象不再被引用,那么这个对象就会被 GC 回收,否则这个对象一直会保存在内存中。

上面的代码,当函数 outSide 被执行后返回了函数 inSide ,函数inSide 的作用域链上有引用到函数 outSide 执行环境的变量 a,这个变量会被函数 fn 引用,所以 a 不会被垃圾回收机制处理掉,而是会留在内存中。这就形成了一个闭包。最后执行 fn() 依然能读取到变量 a

而如果不用闭包的话 ⬇️ ⬇️ ⬇️

1
2
3
4
5
6
7
let a = 1
const inSide = () => {
    a ++
    console.log(a)
}

inSide()  // 2

如果我们再次调用 inSide() 时,结果会一直增加,相应的全局变量 a 的值一直递增。

1
2
3
inSide()  // 3
inSide()  // 4
inSide()  // 5

如果其他函数也需要使用这个变量 a ,那么 inSide 函数会改变 a 的值,使其他地方出错。而且全局变量容易被人修改,比较不安全。

所以,当我们需要在模块中定义一些变量,并希望这些变量一直保存在内存中但又不会 “污染” 全局的变量时,就可以用闭包来定义这个模块。

闭包的缺点

所以如果闭包使用不当,优点就变成了缺点

  • 错误地使用闭包,导致无用的变量不会被垃圾回收机制回收,造成内存消耗;
  • 过多/不恰当地使用闭包可能会造成内存泄漏的问题。

闭包应用对比

假如要对用户的某个数据实现一个自增需求,但又不能改变该用户本身的数据。

  • 定义全局变量可以实现,但是会改变本身的 age
1
2
3
4
5
6
7
8
let age = 18

const fn = () => {
    age ++
    console.log(age)
}

fn()
  • 定义局部变量,但实现不了递增
1
2
3
4
5
6
7
8
9
let age = 18

const fn = () => {
    let age = 18
    age ++
    console.log(age)
}

fn()
  • 闭包可以实现递增并且不污染全局变量
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const age = 20
const outSide = () => {
    let age = 18
    return () => {
        console.log(age++)
    }
}

const fn = outSide()

fn()