跳至正文

JavaScript入门教程笔记(8)-闭包函数和IIFE

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 教程,有修改。

标签:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注