美文网首页
浅谈闭包

浅谈闭包

作者: Rezel | 来源:发表于2018-05-30 14:44 被阅读0次

闭包是函数式语言里面很重要的部分,但是网上很多文章却只讲闭包的应用,而鲜有谈及其本质。

理解闭包的关键在于,知道它的出现是为了解决什么问题。

函数式语言有个重要的语言特性:让函数作为 first class 出现在语言中。而当函数作为参数或者返回值传递的时候,其实本质上传递的是闭包。那为什么不直接传递函数本身,而要传递闭包呢,这个“闭包”的本质又是什么呢?想要解决这些问题,就要求我们站在一个语言设计者的角度去看待。这并不难,我们只需要理解一些必要的概念就可以了。

Lexical Scoping 和 Dynamic Scoping

在对函数求值的时候,有时会有一些微妙的情况。当一个函数体内含有一些外部的变量的时候,这种变量我们称之为“自由变量”。但是不同作用域,对“自由变量”的理解却是不相同的。举个简单的例子讲下。

const x = 2
const f = (y) => x * y
{
  const x = 4
  f(3)
}

在函数 f 里面的 x 就是一个"自由变量",因为 x 不是在 f 里面定义的,所以我们必须在函数的外面找 x 的值。

但是在这段代码里面,有两个地方对 x 进行了绑定。这种情况下应该取哪个值呢?对于不同语言来说,这段代码可能有两种不同的结果。

之所以会出现两种不同结果,就是因为不同语言采取了不同的作用域策略。对于 JavaScript 来说,这段代码的结果是 6,而对于 Emacs Lisp (Emacs 编辑器内置的语言) 来说,这段代码的的结果却是 12。因为 JavaScript 采用了 Lexical scoping,而 Emacs Lisp 采用了 Dynamic scoping。

相比较而言,Lexical scoping 更好一些,因为它是在定义函数的时候确定“自由变量”,是更加符合直觉的。而 Dynamic scoping 则是在函数调用的时候确定“自由变量”。试想下上述代码中 f(3) 如果在距离 f 的定义几百行之外,甚至是在另一个 module,调用者根本不知道 f 引用了一个“自由变量” x,那么很可能会引来很多 bug。

幸运的是,几乎所有现代的函数式语言,都实现了 Lexical scoping,包括 JavaScript。

如何实现 Lexical Scoping

为了实现 Lexical scoping,我们必须把函数做成“闭包”(closure)。闭包只是一种存储结构,为了方便理解,你可以认为它是一个对象,里面存放了两样东西:

  1. 函数本身
  2. 这个函数涉及到的“自由变量”的定义(或者说,是函数定义时候的上下文)

通过将上面例子不准确地转换一下,就可以“显现”闭包了。

const x = 2
const f_closure = {
  fn: function (y)  { return this._x * y },
  _x: x     // 保存了自由变量 x
}
{
  const x = 4
  f_closure.fn(3)   // 调用的是保存在闭包中的函数
}

这段代码虽然并不准确,但却清晰地描述了“闭包”是如何起作用的。当我们定义函数的时候,我们实际上把这个函数以及它需要的“自由变量”打包起来,放在闭包里面。而当我们调用函数的时候,只需要从闭包取出我们想要的一切元素就可以了。

闭包和面向对象的关系

闭包作为一种存储结构,本质上和对象是一样的。这也就不难解释为什么闭包能够存储状态了。为了更好说明这种情况,举个最常见的闭包的例子。

用闭包来实现一个计数器,是这样子的:

const counterCreator = () => {
  let n = 0
  return {
    update: () => n++
  }
}
const c1 = counterCreator()
const c2 = counterCreator()

c1.update()     // 0
c1.update()     // 1
c2.update()     // 0

而用面向对象的方式来实现,则是这样子的:

class Counter {
  constructor () {
    this.n = 0
  }
  
  update () {
    return this.n++
  }
}
const c1 = new Counter()
const c2 = new Counter()

c1.update()     // 0
c1.update()     // 1
c2.update()     // 0

这两种方式中,counterCreator 相当于面向对象中的类 Counter,本质上是一样的。所以写代码的时候不需要太过纠结用哪种方式,因为它们只是形式上的不同而已。

Reference:

关于 Lexical Scoping 和 Dynamic Scoping 的区别,参考了王垠的博文 怎样写一个解释器

相关文章

  • 浅谈闭包

    闭包是函数式语言里面很重要的部分,但是网上很多文章却只讲闭包的应用,而鲜有谈及其本质。 理解闭包的关键在于,知道它...

  • 浅谈闭包

    简要来讲,闭包就是一个函数引用另外一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量。 ...

  • 浅谈闭包

    前端开发者经常在面试时会碰到这样一道题:什么是闭包?首先我们来谈谈闭包的定义:函数A有内置函数B,当内置函数B调用...

  • 浅谈闭包

    js中的闭包 闭包是学习js中永远也绕不过去的一个坎,那么,今天我们就去一段简单的代码开始聊一聊闭包 什么是闭包 ...

  • 浅谈闭包

    《你不知道的JavaScript》这样描述: 当函数可以记住并访问所在的词法作用域时,就产生了闭包, 即使函数是在...

  • 浅谈闭包

    闭包是JavaScript的重点也是难点之一,由于涉及多重知识点,对初学者来说比较难理解。本文将闭包相关的知识点进...

  • 浅谈闭包

    注: 文章摘自 Reng の Blog 定义 闭包是指有权访问另一个函数作用域中的变量的函数 我的理解是,函数内的...

  • 闭包

    浅谈 python 的闭包思想 首先 python的闭包使用方法是:在方法A内添加方法B,然后return 方法B...

  • swift闭包

    浅谈闭包 闭包就是一段代码块,可以在任何地方调用。和函数类似,有参数和返回值。 来看一个简单的例子 带有参数的闭包...

  • 浅谈js闭包

    一.作用域 var x = 0 //全局变量 x var y = 10 //全局变量 y var fun...

网友评论

      本文标题:浅谈闭包

      本文链接:https://www.haomeiwen.com/subject/xcotsftx.html