1 闭包
闭包(closure)是JavaScript语言的一大特色,也是一个难点。理解闭包,首先要理解变量作用域。
作用域有两种:全局作用域和函数作用域。函数内部可以访问全局变量。
var n = 999;
function f() {
console.log(n);
}
f(); // 999
上面代码中,函数 f 可以读取全局变量 n。
但是,函数外部不能访问函数内部的变量。
function f() {
var n = 999;
}
console.log(n); // Error
上面代码中,函数内部声明的变量 n 在外部无法访问。
如果一定要得到函数内的局部变量,只有通过变通方法才能实现,那就是在函数内部再定义一个函数。
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
上面代码中,函数f2在f1内部,则f1定义的变量对f2都是可见的,而函数f1的返回值就是f2,所以就可以在外部获得f1的内部变量了。
函数f2就是闭包,即能够读取其它函数内部变量的函数,也可以把闭包简单理解成“定义在函数内部的函数”。
闭包的最大用处有两个,一是可以访问函数内部的变量,二是让这些变量始终保存在内存中,即闭包可以使它的"诞生环境"一直存在。
function createInc(start) {
return function() {
return start++;
};
}
var inc = createInc(5);
inc(); // 5
inc(); // 6
inc(); // 7
上面代码中,通过闭包inc使得函数内部变量start始终在内存中,每一次调用都是在上一次调用基础上进行的。
为什么会这样呢?原因在于inc始终在内存中,而inc依赖于createInc,因此createInc和内部变量start都始终在内存中,不会在调用结束后就被回收。
注意,外层函数每次运行都会生成一个新的闭包,而这个闭包又会保留外层函数的内部变量,所以内存消耗很大,因此不能滥用闭包。
2 立即调用的函数表达式(IIFE)
圆括号()是一种运算符,跟在函数名之后,表示调用该函数。比如,print()表示调用print函数。
有时需要在定义函数之后,立即调用该函数。这时,不能在函数定义之后加上圆括号,否则产生语法错误。
function(){/*code*/}(); // Error
上面代码错误的原因是,JavaScript把出 现在行首的function一律解释成语句,认为这一段是函数定义,不应该以圆括号结尾,所以就报错了。
解决方法就是让function不要出现在行首,让引擎将其理解成一个表达式。最简单的处理,就是放在一个圆括号里面。
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
上面两种写法都以圆括号开头,避免了错误,这就叫做“立即调用的函数表达式”(Immediately-Invoked Function Expression),简称IIFE。
注意,上面两种写法最后的分号都是必须的。如果没有分号,JavaScript会将它们连在一起解释,可能就会报错。
通常情况下,只对匿名函数使用这种“立即执行的函数表达式”。它的目的有两个:一是不必为函数命名,避免了污染全局变量;二是内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。
// 写法一
var tmp = newData;
processData(tmp);
// 写法二
(function() {
var tmp = newData;
processData(tmp);
}());
上面代码中,写法二比写法一更好,因为完全避免了污染全局变量。
注:本文适用于ES5规范,原始内容来自 JavaScript 教程,有修改。