闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。
1.为什么要使用闭包
我们知道在函数内使用var声明的变量,是函数内的局部变量。那么函数外部自然无法读取函数内的局部变量。 例如:
<script>
function fn(){
var num = 100;
}
console.log(num);
</script>
运行结果:
Uncaught ReferenceError: num is not defined
如何从外部读取局部变量?我们可以在函数的内部,再定义一个函数,再返回这个内部函数的引用。例如:
<script>
function fn(){
var num = 100;
function test(){
return num;
}
return test;
}
var f = fn();
console.log(f());
</script>
运行结果:
100
上面的test函数,就是闭包。
2.闭包的本质
各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
3.使用闭包的好处
闭包可以用在许多地方。它的最大用处有两点: - 外部函数读取其他函数内部变量的函数 - 让某些变量的值始终保持在内存中
外部函数读取其他函数内部变量的函数,例如:
<script>
function fn(){
var num = 100;
function test(){
return num;
}
return test;
}
function haha(){
let f = fn();
let n = f();
console.log(n); //haha函数获取到fn函数内的局部变量。
}
haha();
</script>
运行结果:
100
让某些变量的值始终保持在内存中,例如:
<script>
function fn(){
var num = 100;
increasement = function(){
num++;
};
function test(){
console.log(num);
return num;
}
return test;
}
var f = fn();
f();
increasement();
f();
</script>
运行结果:
100
101
在这段代码中,f实际上就是闭包test函数。它一共运行了两次,第一次的值是100,第二次的值是101。这证明了,函数fn中的局部变量num 一直保存在内存中,并没有在fn调用后被自动清除。
为什么会这样呢?原因就在于fn是test的父函数,而test被赋给了一个全局变量,这导致test始终在内存中,而test的存在依赖于fn,因此fn也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
4.返回匿名函数的闭包
闭包还可以是返回匿名函数,上面的例子改写如下:
<script>
function fn(){
var num = 100;
increasement = function(){
num++;
};
return function (){
console.log(num);
return num;
}
}
var f = fn();
f();
increasement();
f();
</script>
运行结果:
100
101
注意:如果f一旦重新赋值,那么那些始终保持在内存中变量的值,也将被重新初始化。例如:
<script>
function fn(){
var num = 100;
increasement = function(){
num++;
};
return function (){
console.log(num);
return num;
}
}
var f = fn();
f();
increasement();
f = fn(); //f被重新赋值
f();
</script>
运行结果:
100
100