闭包对于脚本语言的开发者是个非常熟悉的概念,而对于一般的Java 开发者却比较陌生,当然Java也早已经引入了闭包,但相对其他语言还是比较晚的。以JavaScript中的闭包为例,掌握闭包的相关知识。
#匿名函数
当初我刚刚接触一些比较高级的JavaScript代码时,经常看到如下的代码:
var a = function(){...}
a();
或者
(function(){
...
})();
这些其实是JavaScript的匿名函数,JavaScript中函数是一等公民,一切皆函数,这就是你经常看到的动不动就出来一个function关键字的原因。那为什么会有这么多的function呢?这有涉及到JavaScript中作用域的概念。
第一段代码中可以理解成a就是一个函数指针,*a()*就是调用函数,使函数执行。
第二段代码第一对括号定义了匿名函数,最后的括号执行这个匿名函数,如果没有最后的括号,函数不会执行。
作用域
前面的匿名函数是JavaScript不同与Java的一个常用语法,至少在Java中是很少看到那样的代码。了解匿名函数对理解作用域很有帮助。
作用域是所有编程语言的一个重要概念,大多数类C语言都有块级的作用域,在代码块(括在花括号中的代码)中定义的变量外部不可以访问。尽管JavaScript语法中也有类似结构,但是它并不支持块级作用域,JavaScript的中变量的作用域叫做作用域链。
作用域链也可以叫做函数作用域。因为可以在函数中定义函数,这样每进入一个函数就会创建一个作用域,即在函数中定义的变量,在本函数中总是可见的,不断调用函数就形成了一个作用域链。链的最前端是当前执行代码中定义的变量的作用域,最末端是被称为window的对象的形成的作用域,前端作用域代码可以访问后端作用域代码的变量,但是反之则不可。你可以认为作用域链是可以顺着链向上查找变量。
下面两个例子
var color = "blue";
function getcolor(){
return color;
}
getcolor();//输出blue
另一个例子
var scope = "global";
function f(){
console.log(scope);//输出undefined
var scope = "local";//变量在这里赋值,但是在函数任何地方都是有定义的,包括第一行
console.log(scope);//输出local
}
f();
第一个例子很好理解,第二个例子中虽然函数中的局部变量scope在第二行定义,但是第一行已经引用了,并且覆盖了同名全局变量。第二个例子中的函数等价于
function f(){
var scope;
console.log(scope);
scope = "local";
console.log(scope);
}
作用域链的概念对理解闭包和with语句很有帮助。
闭包
和其他编程语言类型,JavaScript也采用词法作用域,函数的执行依赖变量的作用域,作用域是函数定义时决定的,而不是函数调用时决定的。JavaScript的函数对象内部包含代码逻辑也包含当前的作用域链。函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性叫做闭包。
从作用域链的角度来说,当调用函数时,闭包所指向的作用域链和定义函数时的作用域链不是同一个作用域链时,事情就会变得非常微妙。
闭包因为会携带包含它的函数作用域,因此会占用更多的内存。
this是JavaScript中的关键字,而不是变量。每个函数的调用都包含一个this值,闭包是无法访问这个this值,因为匿名函数的执行环境具有全局性,其this对象一般指向window(除非使用了call()或apply()改变执行环境),如果想在闭包中访问this,需要在外部函数中将this转存为一个变量。
下面的代码中在第四行后插入var self = this,然后在匿名函数中用self替换this,即可返回l。
var name = "g";
var obj = {
name:"l",
getName:function(){
return function(){
return this.name;
};
}
}
obj.getName()();//返回g
JavaScript中的arguments与之类似。
性能考量
如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。
参考
JavaScript语言精粹
JavaScript权威指南
JavaScript高级程序设计