【旧文新读】解释“闭包”需要几行代码?

新读(2017年10月19日)

本文写于 2013年06月16日,今天做了一点修改,所谓修改,其实只是删去了几句不影响技术内容的话。关于闭包,我最近写了一篇新的文章,提到了静态作用域,相比本文,是对闭包的更深一层的解释,可以作为本文的后续阅读。至于本文,也不是已经没有阅读的价值,因为它说明了以下两点:

  1. 闭包是一个概念,JavaScript 编程中常常用到闭包,但仅仅学会使用闭包,并不等于知道闭包本身是什么;而本文,正是从概念本身解释闭包。
  2. 理解闭包与对象的关系,对于理解闭包非常有帮助,因为二者的功能有很大的重叠。

以下是旧文

关于什么是闭包,很多“高手”会给出一段“特权函数”访问外部作用域变量的 JavaScript 代码,并且告诉你,闭包就是局部变量在函数返回之后继续存在。那么,你如何理解“闭包是穷人的对象”、“对象是穷人的闭包”?

===========================================================

  • 以下内容根据《代码的未来》、“犀牛书”、以及《Thinking in Java》整理

===========================================================

什么是闭包?

闭包(Closure)这个词的意思是封闭,将外部作用域中的局部变量封闭起来的函数对象称为闭包。被封闭起来的变量与封闭它的函数对象有相同的生命周期。

什么是函数对象?

函数对象是作为对象来使用的函数,这里的对象是指编程语言操作的数据。

函数对象与闭包

函数对象不一定是闭包。

C 语言中,可以获取一个函数的指针,并通过指针间接调用此函数。这就是 C 语言中的对象(函数对象也是对象)。但 C 语言中的函数对象不是闭包——它不能访问外部作用域的局部变量。

JavaScript 中,每个函数都有一个与之相关联的作用域链。每次调用 JavaScript 函数的时候,都会为之创建一个新的对象用来保存局部变量,并把这个对象添加至作用域链中。当函数返回时,再将这个对象删除,此对象会被当做垃圾回收。但如果这个函数定义了嵌套的函数,并将它存储在某处的属性里,就意味着有了一个外部引用指向这个嵌套的函数。它就不会被当作垃圾回收,它所指向的变量绑定对象同样不会被回收。

由此可见,JavaScript 中的函数对象是闭包——可以把外部作用域的局部变量“封闭”起来。

什么是对象?

面向对象中的“对象”是指问题空间中的元素(猫、狗)及其在解空间中的表示(new Cat(); new Dog())。对象是过程(函数)与数据的结合。

对象与闭包

对象是在数据中以方法的形式内含了过程,闭包是在过程中以环境的形式内含了数据。所谓“闭包是穷人的对象”、“对象是穷人的闭包”,就是说使用其中的一种方式,就能实现另一种方式能够实现的功能。

应用场景

保护函数内的变量安全:如迭代器、生成器。
在内存中维持变量:如缓存数据、柯里化。