闭包是 javascript 中最重要但经常被误解的概念之一。理解闭包可以解锁其他情况下难以使用的编程模式。在这篇文章中,我们将了解什么是闭包,并通过几个例子来巩固我们对它们的理解。
函数的生命周期
首先我们来看一个简单的函数:
function getValue(){
var a = 1
var b = 2
return a + b
}
var val = getValue()
console.log(val)
// Output: 3
getValue
函数实例化两个变量(a
和 b
),并返回它们的和。将此返回值存储在 val
变量中,然后打印出来。
花点时间思考一下 getValue
函数返回后 a
和 b
的命运。
这些变量仅在函数范围内可见,你不能在 getValue
函数体之外访问 a
或 b
。一旦 getValue
函数返回,就无法获取 a
和 b
的值,它们会被垃圾回收。
闭包实战
现在,让我们看一个不一样的函数:
function newCounter(){
var count = 0
return function(){
count += 1
return count
}
}
var counter = newCounter()
console.log(counter())
// Output: 1
console.log(counter())
// Output: 2
console.log(counter())
// Output: 3
newCounter
的返回值是一个函数。此函数增加 count
的值(其范围在 newCounter
函数体内)并返回它。
与上一个例子类似,没有人可以在 newCounter
函数体之外访问 count
的值。但是,与上一个示例不同的是,newCounter
返回的函数仍然可以访问 counter
。这没有违反任何规则,因为返回函数的主体仍在 newCounter
的主体内。
因此,返回的函数可以间接访问 count
(在我们的示例中,它被分配给 counter
变量),但它仍然与程序中想要访问其值的任何其他代码隔离开来。 这个将 count
变量封闭起来的函数体称为闭包。
这与之前示例中的变量 a
和 b
有何不同?由于在 getValues
闭包中没有任何其他函数引用 a
和 b
,它们将会被垃圾回收,不再存在。而 count
变量将仍然存在于 newCounter
的闭包中,因为它被返回的函数引用了,所以在 newCounter
返回后,它仍然存在于系统内存中。
更多示例
在我看来,理解闭包的最好方法是看它们的实际应用。让我们试着通过几个例子来理解它们是如何工作的:
function greeting(){
var hello = 'hello'
var world = 'world'
function join(){
return hello + ' ' + world
}
return join()
}
console.log(greeting())
// Output: hello world
这看起来与我们之前的示例非常相似,但是这里没有闭包!这是因为与 newCounter
不同,join
函数虽然可以访问 hello
和 world
变量,但它在 greeting
函数体中被调用并退出。greeting
返回的是调用 join
函数的执行结果而不是 join
函数本身。
如果我们稍微修改函数以返回 join
函数,而不是它的结果,那么 hello
和 world
将仍然存在于 greeting
的闭包中:
function greeting(){
var hello = 'hello'
var world = 'world'
function join(){
return hello + ' ' + world
}
return join
}
var sayGreeting = greeting()
console.log(sayGreeting())
// Output: hello world
让我们看另一个例子,我们使用对象方法来获取返回值:
function Counter(){
this.count = 0
this.getCount = function(){
this.count += 1
return this.count
}
}
var counter = new Counter()
console.log(counter.getCount())
// Output: 1
console.log(counter.getCount())
// Output: 2
同样,这似乎与我们之前的计数器示例在做同样的事情,但这里的关键区别在于 this.count
没有封闭在 Counters
闭包内,我们仍然可以访问它:
console.log(counter.count)
// Output: 2
如果我们修改代码使 count
成为一个变量而不是一个对象属性,那么它的作用域将被限制在 Counters
闭包中:
function Counter(){
var count = 0
this.getCount = function(){
count += 1
return count
}
}
var counter = new Counter()
console.log(counter.getCount())
// Output: 1
console.log(counter.getCount())
// Output: 2
console.log(counter.count)
// Output: undefined