logan70 / Blog

写博客的地方,觉得有用的给个Star支持一下~
81 stars 9 forks source link

作用域与闭包 - 闭包的实现原理和作用 #28

Open logan70 opened 4 years ago

logan70 commented 4 years ago

闭包的实现原理和作用

闭包是什么

MDN对闭包描述如下:

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).

闭包是函数创建函数的环境的组合。

标准 可知,JavaScript中函数被创建时都会记录当前词法环境,所以说JavaScript中,每次创建函数,都会生成闭包。

闭包的作用

从技术层面说,静态作用域(词法作用域)是通过闭包实现的。

  1. 函数被创建时会记录所处的词法环境;
  2. 函数被调用时会创建新的词法环境(其中包含一个队外部词法环境的引用),并将外部词法环境引用指向函数创建时所记录的词法环境;
  3. 函数内使用自由变量(不在函数内定义的变量)时,沿着由词法环境组成的作用域链寻找解析。

这样就实现了静态作用域(词法作用域),也就是在编写代码时就能确定变量的解析过程。

我们常说的闭包是什么

我们常说的闭包,也可以说是“有意义”的闭包,具备以下两点特征:

  1. 函数创建时所在的上下文销毁后,该函数仍然存在;
  2. 函数内引用自由变量。

最常见的闭包就是父函数内返回一个函数,返回函数内引用了父函数内变量:

const scope = 'outer'
const genClosure = () => {
  const scope = 'local'
  return () => {
    console.log(scope)
  }
}

const closure = genClosure()
closure()
// <- 'local'

闭包的应用

任何关于闭包的应用总结起来都离不开以下两点:

函数柯里化和偏函数

柯里化:把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数。

// 柯里化前
const getVolume = (l, w, h) => l * w * h
const volume1 = getVolume(100, 200, 100)
const volume2 = getVolume(100, 200, 300)

// 柯里化后
const getVolume = l => w => h => l * w * h
const getVolumeWithDefaultLW = getVolume(100)(200)
const volume1 = getVolumeWithDefaultLW(100)
const volume2 = getVolumeWithDefaultLW(300)

模块化

用于将内部实现封装,仅对外暴露接口,常见于工具库的开发中。

var counter = (function() {
  var privateCounter = 0
  function changeBy(val) {
    privateCounter += val
  }
  return {
    increment: function() {
      changeBy(1)
    },
    decrement: function() {
      changeBy(-1)
    },
    value: function() {
      return privateCounter
    }
  }
})()

模拟块级作用域

最典型的就是ES6之前for循环中使用定时器延迟打印的问题。

for (var i = 1; i <= 3; i++) {
    setTimeout(function() {
        console.log(i)
    }, i * 1000)
}
// <- 4
// <- 4
// <- 4

使用立即执行函数,将i作为参数传入,可保存变量i的实时值。

for(var i = 1; i <= 3; i++){
  (i => {
    setTimeout(() => {
      console.log(i)
    }, i * 1000)
  })(i)
}
// <- 1
// <- 2
// <- 3