JavaScript之闭包

JavaScript之闭包

什么是闭包?

先看一段代码:

1
2
3
4
5
6
7
8
9
10
function a() {
var n = 0;
function inc() {
n++;
console.log(n);
}
inc();
inc();
}
a(); //控制台输出1,再输出2

简单吧。再来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
function a() {
var n = 0;
this.inc = function () {
n++;
console.log(n);
};
}

var c = new a();
c.inc(); // 1
c.inc(); // 2

简单吧。


什么是闭包?这就是闭包!

作用域

要理解闭包,首先必须理解Javascript特殊的变量作用域。

变量的作用域无非就是两种:全局变量和局部变量。

Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。

看个🌰:

1
2
3
4
5
6
7
8
9
10
function outerFun()
{
 var a =0;
 alert(a);  
}
var a=4;
outerFun();
alert(a);

结果是0,4。 因为在函数内部使用了var关键字 维护a的作用域在outFun()内部.

再看个🌰:

1
2
3
4
5
6
7
8
9
10
11
12
function outerFun()
{
 //没有var 
 a =0;
 alert(a);  
}
var a=4;
outerFun();
alert(a);

结果为 0,0 真是奇怪,为什么呢?
作用域链是描述一种路径的术语,沿着该路径可以确定变量的值 .当执行a=0时,因为没有使用var关键字,因此赋值操作会沿着作用域链到var a=4;  并改变其值.

有权访问另一个函数作用域内变量的函数都是闭包。这里inc函数访问了构造函数a里面的变量n,所以形成了一个闭包。
再来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
function a() {
var n = 0;
function inc() {
n++;
console.log(n);
}
return inc;
}

var c = new a();
c(); //1
c(); //2

看看是怎么执行的:
var c = couter(), 这一句couter()返回的是函数inc,那这句等同于var c = inc;
c(), 这一句等同于inc();
注意:函数名只是一个标识(指向函数的指针),而()才是执行函数

后面的三句翻译过来就是:var c = inc; inc(); inc(); 和第一段代码没有区别。

同时在上面的代码中,函数inc就被包括在函数a内部,这时a内部的所有局部变量,对inc都是可见的。但是反过来就不行,inc内部的局部变量,对a就是不可见的。这就是Javascript语言特有的“链式作用域”结构(chain scope),
子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然inc可以读取a中的局部变量,那么只要把inc作为返回值,我们不就可以在a外部读取它的内部变量了吗!

为啥要这样写?

我们知道,js每个函数都是一个个小黑屋,它可以获取外界信息,但是外界却无法直接看到里面的内容。将变量n放进小黑屋里,除了inc函数之外,没有其他办法能接触到变量n,而且在函数a外定义同名的变量n也是互不影响的,这就是所谓的增强“封装性”。

而之所以要用return返回函数标识inc,是因为在a函数外部无法直接调用inc函数,所以return inc与外部联系起来,代码2中的this也是将inc与外部联系起来而已。

常见的陷阱

看看这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createFunctions() {
var result = new Array();
for (var i=0; i < 10; i++) {
result[i] = function() {
return i;
};
}
return result;
}

var funcs = createFunctions();
for (var i=0; i < funcs.length; i++) {
console.log(funcs[i]());
}

咋一看,以为是输出0~9,万万没想到输出10个10?

这里的错陷阱就是:函数带()才是执行函数!单纯的一句var f = function(){alert(“hi)};是不会弹窗的 ,后面接一句f()才会执行函数内部的代码,上面代码的翻译一下就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var result = new Array(), i;

result[0] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数的i替换!

result[1] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数的i替换!
...
result[9] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数的i替换!

i = 10;
funcs = result;
result = null;

console.log(i); //funcs[0]()就是执行 return i 语句,就是返回10
console.log(i); //funcs[0]()就是执行 return i 语句,就是返回10
...
console.log(i); //funcs[0]()就是执行 return i 语句,就是返回10

为什么只垃圾回收了result,但不回收i?因为i还在被function引用着啊。好比一个餐厅,盘子总是有限的,所以服务员会去巡台回收盘子,但装着菜的盘子是不敢收的。当然自己手动到了那就可以回收了( =null ),这就是所谓的内存回收机制。


总结一下

闭包就是一个函数引用另一个函数的变量,因为变量被引用着所以不被回收,因此可以用来封装一个私有变量,这既是优点也是缺点,不必要的闭包只会徒增内存的消耗!另外使用闭包也要注意变量的值是否符合你的要求,因为他就像一个静态私有变量一样。