JavaScript 中的闭包(closure)是什么鬼?

时间:2021-10-27 22:45:31

什么是闭包

此文源自于 *.com 中的一处问答:http://*.com/questions/36636/what-is-a-closure ,有兴趣的朋友可以继续往下看。

兄弟我接触 JavaScript 也好多年,自从 03年 开始有了自己的网站(早就过期了,别搜了,当时用的是免费的只有2M空间还是二级域名),虽然一直在用各种函数式编程(Functional Programming)的特性,但确实没有想过明确这些特性的定义(Definition)。今天挖出来这个概念,理解一下closure到底是什么鬼。

变量作用域(Variable scope)

When you declare a local variable, that variable has a scope. Generally local variables exist only within the block or function in which you declare them.

当你声明了一个本地变量(local variables),那么它就已经有了一个作用域(scope)。一般情况下,本地变量仅存在于声明它的那个代码块或者函数中。

function() {
  var a = 1;
  console.log(a); // 好使
}    
console.log(a); // 没了

If I try to access a local variable, most languages will look for it in the current scope, then up through the parent scopes until they reach the root scope.

如果我想访问一个本地变量,多数编程语言将会在当前的作用域中寻找她,然后再尝试外边的(或者说上一级)那个作用域,直到到最外层(根上)的作用域为止。

var a = 1;
function() {
  console.log(a); // 好使
}    
console.log(a); // 好使

When a block or function is done with, its local variables are no longer needed and are usually blown out of memory.

当一个代码块或者函数结束了,它的本地变量就不再需要了,而且会被清除出内存。

This is how we normally expect things to work.

这也是我们平时期望的那样的代码工作。

闭包是一个持续存在的本地变量作用域(A closure is a persistent local variable scope)

A closure is a persistent scope which holds on to local variables even after the code execution has moved out of that block. Languages which support closure (such as JavaScript, Swift and Ruby) will allow you to keep a reference to a scope (including its parent scopes), even after the block in which those variables were declared has finished executing, provided you keep a reference to that block or function somewhere.

一个闭包是一个持续存在的作用域,它保管好了本地变量,即使运行时已经跑出了那个代码块。一些编程语言他们支持闭包(例如JavaScript, Swift 以及 Ruby),他们允许你保存一个闭包的引用(包括这个闭包的上一级、上N级作用域),即便是那个代码块声明的本地变量已经结束了运行。这些编程语言提供了保存这个引用的方法,在某个位置保管代码块活着函数。

The scope object, and all it’s local variables is tied to the function, and will persist as long as that function persists.

这个作用域对象(scope object)以及它所有的本地变量,都会被绑定到这个函数,而且一直伴随着这个函数的存在而存在。

This gives us function portability. We can expect any variables that were in scope when the function was first defined to still be in scope when we later call the function, even if we call the function in a completely different context.

这提供给了我们在函数应用上的很多便携。它使我们随后运行这个函数的时候,可以保证这个函数最初声明的本地变量会一直存在,哪怕是在别的完全不同的上下文(context)一样能使用到他们。

举例说明

Here’s a really simple example in JavaScript that illustrates the point:

这有个非常简单的 JavaScript 例子,完全可以解释这一点。

outer = function() {
  var a = 1;
  var inner = function() {
    alert(a);
  }
  return inner; // 这里返回了一个函数
}

var fnc = outer(); // 执行 outer 可以得到 inner 函数
fnc();

Here I have defined a function within a function. The inner function gains access to all the outer function’s local variables, including a. The variable a is in scope for the inner function.

这里我定义了一个函数中的函数(inner)。这个函数中的函数赢取了所有它外部函数的本地变量,包括 a。这个变量 a 就存在于内部函数的一个作用域。

Normally when a function exits, all its local variables are blown away. However, if we return the inner function and assign it to a variable fnc, so that it persists after outer has exited, all of the variables that were in scope when inner was defined also persist. The variable a has been closed over – it is within a closure.

正常情况下,当一个函数退出了,那么所有它的本地变量都会灰飞烟灭。然而,如果我们返回了这个内部函数,而且将它附给了一个变量 fnc,那么刚才的内部函数就会在 outer 退出后一直存在,所有在 inner 作用域内声明的变量也会一直存在。这个变量 a 已经被包起来了 – 这就是闭包。

Note that the variable a is totally private to fnc. This is a way of creating private variables in a functional programming language such as JavaScript.

注意到变量 a 相对于 fnc 是完全私有的。这就是函数式编程中创建私有变量(private variables)的一种方法,例如 JavaScript。

As you might be able to guess, when I call fnc() it alerts the value of a, which is “1”.

正如你可能猜测,何时我会运行 fnc() 来弹出变量 a 的值 ‘1’ 呢?

In a language without closure, the variable a would have been garbage collected and thrown away when the function outer exited. Calling fnc would have thrown an error because a no longer exists

在一个不支持闭包的编程语言中,变量 a 早就在 outer 退出后被当成垃圾回收来。运行 fnc 就会跑出一个错误,说是这个变量 a 已经不存在了。

In JavaScript, the variable a persists because variable scope is created when the function is first declared, and persists for as long as the function continues to exist.

在 JavaScript 中,变量 a 就会一直存在,因为变量作用域已经在函数第一次声明的时候被创建出来了,而且一直被保存下来。

a belongs to the scope of outer. The scope of inner has a parent pointer to the scope of outer. fnc is a variable which points to inner. a persists as long as fnc persists. a is within the closure.

a 属于 outer 作用域。inner 作用域有个父指针(parent pointer)指向 outer 作用域。fnc 变量指向了 innera 一直伴随着 fnc 的存在而存在。a 就是在传说中的闭包之中。