1、JavaScript的闭包和Java的匿名内部类:
在学习java嵌套类的时候,发现匿名类、内部类与之前学习js闭包有很多相似之处深入了解后发现
他们在解决问题上的思路是上有很多相同之处。并不是说java和js有很多的相同之处。即使在这闭包这个问题上,也能看出js和java这两门语言之间很大的一个不同点,js闭包中内部函数访问的是局部变量本身,而java匿名内部类的方法中用到的局部变量则是局部变量值的拷贝。希望本文没有给你造成这种js与java类似的误解。
2、javascript闭包的介绍与使用
MDN 对闭包的定义为:闭包是指那些能够访问自由变量的函数。
自由变量:自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
由此,我们可以看出闭包共有两部分组成:
闭包 = 函数 + 函数能够访问的自由变量。
举个例子:
var a = 1;
function foo() {
console.log(a);
}
foo();
其中,foo 函数可以访问变量 a,但是 a 既不是 foo 函数的局部变量,也不是 foo 函数的参数,所以 a 就是自由变量。
这样,函数 foo + foo 函数访问的自由变量 a 构成了一个闭包。
ECMAScript中,闭包指的是:
1、从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
2、从实践角度:以下函数才算是闭包:
- 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回);
- 在代码中引用了自由变量。
下面来一道经典的闭包面试题:
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = (function (i) {
return function(){
console.log(i);
}
})(i);
}
data[0]();
data[1]();
data[2]();
当执行到 data[0] 函数之前,此时全局上下文的 VO 为:
globalContext = {
VO: {
data: [...],
i: 3
}
}
而当执行到 data[0] 函数之前,此时全局上下文的 VO 为:
globalContext = {
VO: {
data: [...],
i: 3
}
}
当执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变:
data[0]Context = {
Scope: [AO, 匿名函数Context.AO globalContext.VO]
}
匿名函数执行上下文的 AO 为:
匿名函数Context = {
AO: {
arguments: {
0: 0,
length: 1
},
i: 0
}
}
data[0]Context 的 AO 并没有 i 值,所以会沿着作用域链从匿名函数 Context.AO 中查找,这时候就会找 i 为 0,找到了就不会往 globalContext.VO 中查找了,即使 globalContext.VO 也有 i 的值(值为3),所以打印的结果就是 0。
对js来说,函数内部可以直接读取全局变量,而在函数外部自然无法读取函数内的局部变量。出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,一般情况下,这是办不到的,只有通过变通方法才能实现。js里的这种机制就叫闭包(closure)。
我的理解是,闭包就是能够读取其他函数内部变量的函数。通过闭包,上述代码中,局部变量 i 的lifetime延长,其内存并没有随着函数data[i] 的完成而被释放。其底层的实现机制可能类似于是,将闭包捕捉的局部变量放在堆上而不是栈上。
3、java内部类介绍与使用
下面是一段经典的新建线程的Java代码:
new Thread(new Runnable() {
@Override
public void run() {
//do something...
}
}).run();
读一下Thread类的源码就会发现, Thread对象初始化的时候会将传入的Runnable对象作为一个Field存入. 在需要调用的时候则会调用这个Runnable对象的run()方法. Java本身没办法直接传递函数, 因此在需要传递函数的时候需要将方法封装在一个接口对象里进行传递, 这个接口还有专门的名字, 叫作函数式接口(Functional Interfaces).;
说白了, Java里面的闭包就是一个匿名内部类. 之所以说Java的闭包是一个"残疾"的, 是因为它不会像JS的闭包那样保存运行环境(其实就是将在function内部保存一个指向function外部的指针, 在外部作用域被销毁时不会清除这块内存),
匿名内部类的使用:
public Runnable f(int x) {
int i = 0;
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(i);
i = 10;
}
}
i = 100;
return r;
}
在java中,一般来说,在方法执行完后,其方法内的局部变量也随之释放。而当java匿名内部类的方法中用到局部变量时,使用的是变量的copy,而不是变量本身,所以为了在方法执行完以后,这个局部变量的copy没有被释放掉,这个局部变量的copy就没有被分配到这个方法的栈上,而是分配到对上,从而延长了他的lifetime。(参看上述java代码例子)而之所以要求变量是final的其实是要求该局部变量是不可变的,因为当局部变量i改变时,而匿名内部类r已经返回,而r拿到的是i原来值(i=0)的copy,i后来的值得改变,r是不知道,那r的存在就没有太多的实际意义,这显然是不合适的。所以java为了克服这个问题,就规定java匿名内部类的方法中用到的局部变量必须是不能修改的(即要么定义为final类型的,要么是在方法内不是不修改的),这样方法内局部变量和java匿名内部类的方法中用到的局部变量就保持了一致。有没有种是js阉割版闭包的感觉~