闭包是JavaScript一个重要的感念,但到底什么是闭包,很多人却说不太清楚。关于闭包有两个大家都比较认同的定义:
定义1
A closure is a function that has access to the parent scope, even after the scope has closed.
定义2
A closure is the combination of a function and the lexical environment within which that function was declared.
要想理解闭包,就先要理解Scope(作用域)的概念。作用域就是一个JavaScript变量在何时何地可以被其他函数使用。
当你创建了一个JavaScript函数(function)时,这个函数是可以访问它被创建时内部和外部的变量。举一个栗子:
function sayHi() {
var who = 'Chris';
console.log(who);
}
sayHi(); // 'Chris'
console.log(who); // Uncaught ReferenceError: who is not defined
栗子中的 who
就是函数内部变量,即函数内部作用域的变量。变量 who
不能被全局作用域的代码访问,因为它在 sayHi
这个函数的内部作用域内。但如果将代码修改成:
var who = 'Chris';
function sayHi() {
console.log(who);
}
sayHi(); // 'Chris'
console.log(who); // 'Chris'
who
在全局作用域中,对于 sayHi
来说它是自己的外部作用域中的变量,所以可以被访问到。
再举一个函数嵌套函数的栗子:
function toWho() {
return function sayHi() {
var who = 'Chris';
console.log(who);
}
}
var greeting = toWho();
greeting(); // 'Chris'
这段代码中,调用 toWho
后返回的是一个函数,在返回的这个函数被调用时,它(sayHi
)可以访问自己内部定义的变量(who
)。如果我们挪动一行代码,看看会有什么变化。
function toWho() {
var who = 'Chris';
return function sayHi() {
console.log(who);
}
}
var greeting = toWho();
greeting(); // 'Chris'
可以看到,控制台上会打出 who
这个变量的值(Chris)。当toWho函数被调用后,它的函数体已经执行结束,闭包也已经关闭,返回了一个新函数给变量 greeting
。当 greeting
被调用时,执行的是 sayHi
的函数体。尽管在函数 sayHi
中没有定义 who
变量,但当 greeting
被调用的时候还是可以访问到 sayHi
的父作用域的变量。到此,我们刚刚使用了一个闭包。
闭包就是一个函数在它的作用域关闭后,还可以访问它父作用域的变量。
内部函数 sayHi
创建了一个闭包,来维护它父作用域 toWho
函数中的变量。所以当被 greeting
调用时,who
这个变量可以被访问到。
每一个JavaScript函数都会有一个闭包,你不需要做额外的工作来显性的声明闭包。
再来看一个闭包的栗子:
function sayHi(a) {
return function(b) {
return `${a} say hi to ${b}`;
};
}
var x = sayHi('Chris');
var y = sayHi('Kitty');
x('David'); // 'Chris say hi to David'
y('Will'); // 'Kitty say hi to Will'
变量 a
在匿名函数的闭包中,所以当 x
和 y
函数被调用时,a
是可以被访问到的。