函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。
先拿 MDN 上的一个简单的 🌰 来说:
function init() {
var name = "Mozilla"; // name 是一个被 init 创建的局部变量
function displayName() { // displayName() 是内部函数,一个闭包
alert(name); // 使用了父函数中声明的变量
}
displayName();
}
init();
init()
创建了一个局部变量 name
和一个名为 displayName()
的函数。displayName()
是定义在 init()
里的内部函数(仅在 init()
函数体内可用),它没有自己的局部变量,
然而它可以访问到外部函数的变量,所以 displayName()
可以使用父函数 init()
中声明的变量 name
。
这个就是最简单的一个闭包实现。
不过要了解闭包,就得先了解 JavaScript 变量的作用域,变量的作用域分为 全局变量
和 局部变量
例如全局变量
var a='test'
function fn(){
console.log(a)
}
fn() // test
全局变量可以在函数内使用,函数内部的变量就不可以在函数外部被调用了
function fn(){
var a = 'test'
}
console.log(a) // Uncaught ReferenceError: a is not defined
在函数内部
可以使用在外部函数内声明的变量,例如文章开头提到的例子,并且每次函数被执行是在内存里都会开辟一个新的区块,所以每次一执行的函数并不是指向同一个内存地址。
但是有些时候我们需要在函数外部调用这个函数内的局部变量。
这就是Javascript语言特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然内部函数
可以读取 外部函数 中的 局部变量,那么只要 内部函数 作为 返回值
,我们不就可以在函数外部读取它的 内部变量
了吗!
的确,大都数情况我们都是在 函数内部定义一个函数,并且把这个内部函数 return
出来,从而来达到目的。
🌰 例如这个例子
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
上方的例子就可以看到在把 内部函数
赋值给 外部变量
之后,可以继续在之前的运行结果之上进行加运算。
当然也可以修改被调用的外部函数变量,例如这个累加的例子:
function makeAdder(x) {
var x = x
return function(y) {
x += y
return x;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(5)); // 10
console.log(add5(10)); // 20
console.log(add10(5)); // 15
console.log(add10(10)); // 25
function makeAdder(x) {
var x = x
return function(y) {
x += y
return x;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(5)); // 10
console.log(add5(10)); // 20
console.log(add10(5)); // 15
console.log(add10(10)); // 25
一句话形容闭包
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。
所以,在本质上,闭包就是将 函数内部 和 函数外部 连接起来的一座桥梁。
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
ES6 普及之后真的很少有使用到闭包了,const
和 let
的出现方便了太多了。
#### 最后给你们看一个 阮一峰 老师文章里边的例子,乍一看 nAdd()
我懵了半分钟。
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
遇到的疑问
闭包是否一定要被外部变量保存(保持被引用)
并不是只有被外部变量引用的子函数叫做闭包,没有内存地址没有被保持引用的函数也可以是闭包;
因此可以把闭包简单理解成”定义在一个函数内部的函数”。
引发问题:变量提升,到底会被提升到哪里?
以前一直以为 变量提升 会把使用
var
声明的变量提升到全局环境下。但是这次再写demo的时候发现原来是提升当前函数的顶部,并不是到全局下的。function a(){ function b(){ console.log(str) } var str='test text' }
在 变量提升 下可以这样理解
function a(){ var str function b(){ console.log(str) } str='test text' }