我们本章学习搞懂三个概念:
1.什么是闭包?
2.闭包是什么时候创建的?
3.对闭包的理解?
什么是闭包
首先来看维基百科上闭包的定义:
闭包是引用了自由变量的函数,这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。
闭包也可看成是由函数和与其相关的引用环境组合而成的实体。
有没有觉得过于抽象,别急,我们用代码说话:
function f1(){
var m=10;
var b=20;
function f2(){
console.log(m);
}
f2();
}
f1();//10
对上述代码来说,f2引用了变量m,那么a就和f2函数一同存在,f2即为该段代码中的闭包。
可概括为:一个函数在另一个函数中定义,内部的函数就称之为闭包函数,它可以访问到父函数的成员。
【查看闭包的方式:先按f12,然后给console.log那一行设置一个断点,既可以看到Closure中的内容】

还记得上面说的:“即使离开了创造它的环境也不例外”吗?
function f1(){
var a=10;
var b=20;
return function f2(){
console.log(a);
}
}
var result = f1(); //此时变量result接收的就是function f2
result(); //输出结果为10
创造变量a的环境是f1,重新调用一个就是重新开了一个词法环境;第二个result相当于已经离开了创造它的环境。
即使f2已经离开了f1的环境,但是依然能访问到a。
闭包是什么时候创建的
仍以上述代码为例:调用f1的时候,会调用f1的词法环境;把a和b加进去;运行到return f2的时候已经创建了闭包。
闭包的理解
- 一个函数没用到父函数的东西,会产生闭包吗?
function f1(){
var m=10;
function f2(){
console.log("aaaa");
}
f2();
}
f1();
答案是:不会。
此段代码的f2并没用到父函数f1中的变量m,通过f12查看发现并没产生Closure。
- 若没用到父级中的东西,而是用到了父级中的父级的东西,即下段代码中的f3用的是f1的变量,此刻会产生闭包吗?
function f1(){
var m=10;
function f2(){
var n=20;
function f3() {
console.log(m);
}
f3();
}
f2();
}
f1();
答案是:会。
闭包的好处
- 减少全局变量
function add() {
var a=0;
a++;
alert(a);
}
add();
add();
此时的结果是:弹出两次1. 实际想要的是调用一次加1的效果,弹出1,2.
先使用全局变量进行改进:
var a=0;
function add() {
a++;
alert(a);
}
add();
add();
此时能达到效果。
再利用闭包进行改进:
function f() {
var a=0;
return function() {
a++;
alert(a);
}
}
var result = f();
result();
result();
此时也能达到效果,且减少了全局变量的污染。
- 减少传递给函数的参数变量
function calFactory(base){
return function(max) {
var total = 0;
for(var i=1;i<=max;i++){
total += i;
}
return total + base;
}
}
var adder = calFactory(2); //此时捕获的base是2
alert(adder(3));//结果为8 即2+(1、2、3)
var adder2 = calFactory(1); //此时捕获的base是1
alert(adder2(3));//结果为7,即1+(1,2,3)
- 封装
(function () {
var m=0;
function getM() {
return m;
}
function setM(val) {
m=val;
}
window.g = getM; //此处不是函数调用
window.f = setM;
})();
f(12);
alert(g()); //此时输出为12
闭包使用的注意点
- 对捕获的变量只是引用,不是复制
function f() {
var num =1;
function g() {
alert(num);
}
num++;
g();
}
f();//此时输出结果为2
为什么输出结果是2而不是1?
代码执行过程如下:
先创建f的词法环境,f.le,然后到function g的时候闭包已经产生了,g.scope指向f.le,然后num++为2,是在f的词法作用域中的,最后执行g的时候,从f.le中取到num。
- 父函数每次调用会产生不同的闭包
function f() {
var num =1;
return function () {
num++;
alert(num);
}
}
var result = f();
result(); //2
result();//3
var result1 = f();
result1();//2
result1();//3
第一次调用f时,创建词法作用域f.le,此时即使调用了两次f,仍然都是同一个词法作用域,所以会输出2,3; 第二次调用f时,创建了新的词法作用域,此时后面两次result1调用,也都是同一个的新的这个词法作用域。
- 循环中问题
<div id="1">1</div>
<div id="2">2</div>
<div id="3">3</div>
<script type="text/javascript">
for(var i=1;i<=3;i++){
var ele = document.getElementById("i");
ele.onclick=function(){
alert(i);
}
}
</script>
结果点击id为1,2,3的弹框内容均为4;
将代码修改如下:
for(var i=1;i<=3;i++){
var ele = document.getElementById(i);
ele.onclick=(function (id) {
return function(){
alert(id);
}
})(i);
}
立即执行函数中包裹着一个闭包;立即执行函数传回的参数值为i,通过id进行接收,接收到i,然后再弹出,即对应div1,div2,div3弹出1,2,3,而不是4,4,4,用到了第二条:父级函数每调用一次,就产生一个新的闭包。