JavaScript闭包的理解和用途

1. 什么是闭包?

 在了解闭包之前,应当了解一下JavaScript的作用域和作用域链的概念。详细可看《JavaScript作用域及作用域链的详解》
 闭包就是能够读取其他函数内部变量的函数。
 由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成”定义在一个函数内部的函数”。
 所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

2.从代码理解闭包

1
2
3
4
5
6
7
8
var scope = "global scope"; //全局变量
function checkscope(){
var scope = "local scope"; //局部变量
function f(){return scope} //返回local scope
return f
}
var C = checkscope() //返回local scope
C()

 这是简单的闭包,函数checkscope的内部函数f被函数checkscope外的一个变量C引用。C能够读取checkscope函数内部变量的函数f。
这就形成了闭包的概念,闭包就是能够读取其他函数内部变量的函数

3.闭包的用途

3.1 用闭包模拟私有方法,私有变量

 使用闭包来定义公共函数,并令其可以访问私有函数和变量

以下使用例子Counter,实现一个计数器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var Counter = (function() {
var privateCounter = 0; //私有变量
function changeBy(val) { //私有方法
privateCounter += val;
}
return {
increment: function() { //计数器加1
changeBy(1);
},
value: function() {
return privateCounter;
}
}
})();
Counter.value(); //公有方法
Counter.increment(); //公有方法
Counter.privateCounter() //undefined

 Counter包含两个私有项:名为privateCounter的变量和名为 changeBy的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的两个公共函数访问。

3.2 立即执行函数

a.什么是立即执行函数?

 立即执行函数表达式,Immediately-Invoked Function Expression,简写为IIFE

b.立即执行函数的作用?

 IIFE只有一个作用:创建一个独立的作用域
 因为JavaScript中只有全局作用域和函数作用域。为了避免变量的污染,尽可能地少设置全局变量。所以我们就用立即执行函数表达式。

c.IIFE的经典场景
1
2
3
4
5
6
7
8
9
var arr = [];
for(var i = 0; i< 5; i++){
(function(i){
arr[i] = function(){
return i;
}
})(i)
}
arr[2]() // 2
d.闭包与立即执行函数表达式的关系

 闭包与立即执行函数它们是合作的关系,经常会看到立即执行函数中会存在函数,那这个函数就是闭包了

3.3. 封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var person=function()
{
var name='Jessy';// 定义成员函数,封装属性,外界无法直接使用
return{
setName:function(data)
{
name=data;
},
getName:function()
{
return name;
}
}
}();
person.name//undefined,不能访问
person.getName()//只能通过get/set方法调用
person.setName('Jessy')

3.4. 实现类和继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//Person 类
function Person(){
var name = "Jessy";
return{
setName:function(data)
{
name=data;
},
getName:function()
{
return name;
}
}
}
//定义子类
var Jessy = function(){}
//集成Person类
Jessy .prototype = new Person()
//定义子类方法
Jessy.prototype.eat = function(){
console.log("eat.....");
}
var jessy = new Jessy()
//子类实例可以方法自身定义的eat方法和从父类中继承而来的setName/getName
jessy.eat()
jessy.getName()
jessy.setName("Jessy")

作者: Jessy Hong