引:最近项目前端用了angular2,里面使用的TypeScript,其实很多和Java挺像的,学起来还是挺快的。但是里面的很多变量的原理都是根据闭包来实现的,那就好好了解一下闭包。
闭包定义
根据Mozilla开发者文档定义:
闭包是指向独立变量的“函数”,用通俗的话说就是会“记住”它创建时的环境。
闭包涉及的主要概念
- 作用域链
作用域链是函数在定义的时候创建的,用于寻找使用到的变量的值的一个索引。它内部的规则是,把函数自身的本地变量放在最前面,把自身的父级函数中的变量放在其次,把再高一级函数中的变量放在更后面,以此类推直至全局对象为止.当函数中需要查询一个变量的值的时候,js解释器会去作用域链去查找,从最前面的本地变量中先找,如果没有找到对应的变量,则到下一级的链上找,一旦找到了变量,则不再继续.如果找到最后也没找到需要的变量,则解释器返回undefined.
- 内存回收机制
一个函数在执行开始的时候,会给其中定义的变量划分内存空间保存,以备后面的语句所用,等到函数执行完毕返回了,这些变量就被认为是无用的了.对应的内存空间也就被回收了.下次再执行此函数的时候,所有的变量又回到最初的状态,重新赋值使用.但是如果这个函数内部又嵌套了另一个函数,而这个函数是有可能在外部被调用到的.并且这个内部函数又使用了外部函数的某些变量的话.这种内存回收机制就会出现问题.如果在外部函数返回后,又直接调用了内部函数,那么内部函数就无法读取到他所需要的外部函数中变量的值了.所以js解释器在遇到函数定义的时候,会自动把函数和他可能使用的变量(包括本地变量和父级和祖先级函数的变量(自由变量))一起保存起来.也就是构建一个闭包,这些变量将不会被内存回收器所回收,只有当内部的函数不可能被调用以后(例如被删除了,或者没有了指针),才会销毁这个闭包,而没有任何一个闭包引用的变量才会被下一次内存回收启动时所回收.
闭包现象
1 | var results = []; |
解析:其实这里return出来的是一个function(我们可以理解为他是一个字符串,还没有执行),等到我们去执行他的时候,只保存了他上一级的作用域链里面的i的索引,那个时候i已经是3了。
闭包解决
让内部函数在循环创建的时候立即执行,并且捕捉当前的索引值,然后记录在自己的一个本地变量里.然后利用返回函数的方法,重写内部函数,让下一次调用的时候,返回本地变量的值,改进后的代码:1
2
3
4
5
6
7
8
9
10
11var results = [];
for (var i = 0; i <3; i++) {
results[i] = (function(j) {
return function(){
console.log(j);
}
})(i);
}
results[0](); //0
results[1](); //1
results[2](); //2
我们发现通过立即执行表达式就可以解决闭包的现象得到我们想要得到的现象。
闭包应用
闭包与静态变量
前面就说TypeScript与Java很像,所以类中的静态变量也是有的。
TypeScript代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Counter {
private static COUNTER = 0;
constructor() {}
private changeBy(val) {
Counter.COUNTER +=val;
}
public increment() {
this.changeBy(1);
}
public decrement() {
this.changeBy(-1);
}
public value() {
return Counter.COUNTER;
}
}
编译之后的js代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var Counter = (function () {
function Counter() {
}
Counter.prototype.changeBy = function (val) {
Counter.COUNTER += val;
};
Counter.prototype.increment = function () {
this.changeBy(1);
};
Counter.prototype.decrement = function () {
this.changeBy(-1);
};
Counter.prototype.value = function () {
return Counter.COUNTER;
};
Counter.COUNTER = 0;
return Counter;
}());
从js代码可以看书静态变量COUNTER是属于Counter类的,并不属于对象原型。所有Counter实例都共享Counter的同一个闭包上下文环境(COUNTER)。所以COUNTER会表现像单例一样。
闭包和私有成员
TypeScript由于性能原因并没有使用闭包来模拟私有变量,他使用过编译检查机制来形成私有变量的特性。但是我们可以使用闭包来实现私有变量。
js代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function makeCounter() {
var COUNTERR = 0;
function Counter() {
}
function changeBy(val) {
COUNTER += val;
};
Counter.prototype.increment = function () {
this.changeBy(1);
};
Counter.prototype.decrement = function () {
this.changeBy(-1);
};
Counter.prototype.value = function () {
return COUNTER;
};
return new Counter();
};
从上面的代码可以看出,每一个新的makeCounter实例都拥有自己的上下文环境,其他实例访问不了。
总结
学习这么久的js发现,其实闭包真的无处不在,需要好好学习,好好总结,如有不对,也希望大家能够指出。
参考
书籍:Learning TypeScript中文版