闭包

对于JSer来说,闭包是一个难懂,但必须征服的概念,今天我们就来谈谈这个闭包…

  • 1. 变量的作用域
    在函数内部生命的局部变量在函数外面是访问不到的

    1
    2
    3
    4
    5
    6
    var func = function(){
    var a = 1;
    console.log(a) //1
    };
    func();
    console.log(a) //error

    函数内部的变量在函数体内找不到时,就会沿着作用域链往外层搜索

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var b = 1;
    var func1 = function(){
    var c = 2;
    var func2 = function(){
    var d = 3;
    console.log( c ); //2
    console.log( b ); //1
    };
    func2();
    console.log( d ) //error
    };
    func1();
  • 2. 变量的生存周期
    对于在函数内用var声明的变量来说,退出函数时,这些局部变量就会被销毁,

    1
    2
    3
    4
    5
    var func3 = function(){
    var e = 2; //退出函数后就会被销毁
    alert(2);
    };
    func3();

    然而对于闭包来说

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var func4 = function(){
    var a = 1;
    return function(){
    a++;
    console.log(a);
    }
    };
    var f = func4();
    f(); //2
    f(); //3
    f(); //4

Why?
这是因为当执行var f = func4();时,f返回了一个匿名函数的引用,它可以访问到func4()被调用时产生的环境,而局部变量一直处于这个环境中,既然局部变量所在的环境还能被外界访问,所以就有了不被销毁的理由

接下来我们来看一个闭包的经典应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<link rel="stylesheet" href="">
</head>
<body>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</body>
</html>
1
2
3
4
5
6
7
<!-- js -->
var nodes = document.getElementsByTagName('div');
for(var i=0,l=nodes.length;i<l;i++){
nodes[i].onclick = function(){
console.log(i)
}
};

上面这个例子不管你点击哪个,它弹出的都是5,这是因为div节点的onclick事件是异步触发的,当事件触发时,循环早已结束,此时的i就是5,那么如何来修改呢?看以下两种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 方法一 -->
var nodes = document.getElementsByTagName('div');
for(var i=0,l=nodes.length;i<l;i++){
(function(i){
nodes[i].onclick = function(){
console.log(i)
}
})(i)
};

<!-- 方法二 -->
var nodes = document.getElementsByTagName('div');
for(var i=0,l=nodes.length;i<l;i++){
nodes[i].onclick = (function(i){
return function(){
console.log(i)
}
})(i)
}

  • 3. 闭包的更多作用
    • 1>. 封装变量
1
2
3
4
5
6
7
8
var mult = function(){
var a = 1;
for(var i=0,l=arguments.length;i<l;i++){
a *=arguments[i]
}
return a;
};
console.log( mult(2,3,4) ) //24

上面的方法我们还可以加入缓存机制,这样重复的就不用在计算了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var mult = (function(){
var cache = [];
var calculate = function(){
console.log(arguments)
var a = 1;
for(var i=0,l=arguments.length;i<l;i++){
a *=arguments[i]
}
return a;
};
return function(){
var arg = [].join.call(arguments,',');
if(cache[arg]){
return cache[arg]
}
return cache[arg] = calculate.apply(null,arguments)
}
})()
console.log( mult(2,3,4) );

  • 2>.延续变量‘寿命’
1
2
3
4
5
var report = function(src){
var img = new Image();
img.src = src;
}
report('http://7xqjce.com1.z0.glb.clouddn.com/weixin.jpeg')

如上情况,图片数据会丢失,因为并非每个http请求都是成功的,,又因为img是局部变量,当函数调用完后,img就会销毁,此时数据还没有传输完整

使用闭包修正如下:

1
2
3
4
5
6
7
8
9
var report = (function(){
var imgs = [];
return function(src){
var img = new Image();
imgs.push( img )
img.src = src;
}
})();
report('http://7xqjce.com1.z0.glb.clouddn.com/weixin.jpeg')

  • 3>. 闭包与面向对象设计

    对象以方法的方式包含了过程,而闭包在过程中以环境形式包含了数据,通常用面向对象能实现的功能,用闭包也能实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 闭包形式 -->
var extent = function(){
var val = 0;
return {
call : function(){
val ++
console.log(val)
}
}
};
var extent = extent();
extent.call(); //1
extent.call(); //2
extent.call(); //3
1
2
3
4
5
6
7
8
9
10
11
<!-- 面向对象形式 -->
var extent = {
val : 0,
call :function(){
this.val++;
console.log(this.val)
}
};
extent.call(); //1
extent.call(); //2
extent.call(); //3
文章目录
  1. 1. 接下来我们来看一个闭包的经典应用
    1. 1.1. 如上情况,图片数据会丢失,因为并非每个http请求都是成功的,,又因为img是局部变量,当函数调用完后,img就会销毁,此时数据还没有传输完整