浅谈闭包

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

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

函数式语言有个重要的语言特性:让函数作为 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 的区别,参考了王垠的博文 怎样写一个解释器

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,313评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,369评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,916评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,333评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,425评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,481评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,491评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,268评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,719评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,004评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,179评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,832评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,510评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,153评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,402评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,045评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,071评论 2 352

推荐阅读更多精彩内容

  • 闭包: 百度百科:官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变...
    尚有阅读 1,942评论 0 1
  • js中的闭包 闭包是学习js中永远也绕不过去的一个坎,那么,今天我们就去一段简单的代码开始聊一聊闭包 什么是闭包 ...
    枫尘逍遥阅读 345评论 0 2
  • 一、什么是闭包?## 简言之,闭包是由函数引用其周边状态(词法环境)绑在一起形成的(封装)组合结构。在 JavaS...
    朝槿123阅读 429评论 0 1
  • [toc] 1. 前言 在阅读Think in java时,关于内部类的作用中出现了闭包这个词。于是开始百度,了解...
    AItsuki阅读 9,411评论 7 39
  • 女儿有一天晚上特别想看《花木兰》的电影,可是我们之前约定好的规则是只能周末看电影。 所以,当她看完两集较短的动画片...
    紫薇琴心阅读 459评论 2 1