模仿块级作用域-立即执行函数


前言:最近在细读Javascript高级程序设计,对于我而言,中文版,书中很多地方一笔带过,所以用自己所理解的,尝试细致解读下。如有纰漏或错误,会非常感谢您的指出。文中绝大部分内容引用自《JavaScript高级程序设计第三版》。


JavaScript没有块级作用域的概念(ES5中没有)。这意味着在块语句定义的变量,实际上是在函数中而非语句中创建的。

function outputNumbers(count) {
    for( var i = 0; i < count; i ++ ) {
        console.log(i); // 0,1,2,3,4,5,6,7,8,9
    }
    console.log(i); //10
}

outputNumbers(10);

在函数outputNumbers()中定义了一个for循环, 而变量i的初始值被设置为0。
在Java、C++等语言中,变量i只会在for循环的语句中有定义,循环一旦结束,变量i就会被销毁。

可是在JavaScript中,变量只是定义在函数outputNumbers()的活动对象中的,因此从它有定义开始,就可以在函数内部随处访问它。

即使像下面这样错误地重新声明同一个变量,也不会改变它的值。

function outputNumbers(count) {
    // 注意变量提升 var i
    for(var i = 0; i < count;  i++) {
        console.log(i); // 0, 1, 2, 4, 5, 6, 7, 8, 9
    };

    var i;
    console.log(i); // 10
}

outputNumbers(10);

JavaScript从来不会告诉你是否多次声明了同一个变量;
遇到这种情况,它只会对后续的声明视而不见(不过,它会执行后续声明中的变量初始化)。

匿名函数可以用来模仿块级作用域并避免这个问题(其实就是立即执行函数), 一定要注意变量的生命周期,局部变量只在执行环境中存在,块级作用域其实就是立即执行函数,即刻产生一个作用域,块级作用域里面的变量只在块级作用域里面,避免了变量污染,隔离出一个独立的作用域(私有作用域)。

//语法如下

(function(){
    //这里是块级作用域
})()

以上代码定义并立即调用了一个匿名函数。
将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式。
而紧随其后的另一对圆括号会立即调用这个函数。

这种语法,确实很不好理解。

来看下下面的例子

var someFunction = function() {
    //这里是块级作用域
};
someFunction();

这个例子先定义了一个函数,然后立即调用它。定义函数的方式是创建一个匿名函数,并把匿名函数赋值给变量someFunction。而调用函数的方式是在函数名称后面添加一对圆括号,即someFunction()。那么我们可不可以,声明函数和调用函数写在一块呢?


function(){
    //这里是块级作用域
}(); //出错

这段代码会导致语法错误,因为JavaScript将function关键字当做一个函数声明的开始。而函数声明后面不能跟圆括号。

如果我们在function关键字前面加一个运算符,这样就不会让JavaScript将function关键字当做函数声明的开始。解析器,就会依次从左往右解析代码。

//解析过程 运算符 => 函数声明function => 调用()
!function(){
    console.log("hi");
}(); // "hi"

+function(){
    console.log("hello");
}(); // "hello"


-function(){
    console.log("World");
}(); // "World"

(function(){
    console.log("ciao");
})(); // "ciao"

无论在什么地方,只要临时需要一些变量,就可以使用私有作用域。

function outputNumbers(count) {

    //立即执行函数,隔离出一个独立的作用域,避免变量的污染
    //同时也是一个闭包, 能访问到外部函数中的count
    (function(){
        for(var i= 0; i < count;  i++) {
            console.log(i);
        }
    })();

    console.log(i); // error
    
}

outputNumbers(10); // 0,1,2,3,4,5,6,7,8,9 
//i is not defined

这种技术(使用私有作用域),经常在全局作用域使用。

**从而限制向全局作用域中添加过多的变量和函数。
因为会产生变量污染,私有作用域可以访问到全局变量,而全局变量访问不到私有作用域里面的变量。**

一般来说,我们都应该尽量少向全局作用域中添加变量和函数。
在一个由很多开发人员共同参与的大型应用程序中,过多的全局变量和函数很容易导致命名冲突。

而通过创建私有作用域,每个开发人员都可以使用自己的变量,又不必担心搞乱全局作用域。

(function(){

    var now = new Date();

    if(now.getMonth() == 0 && now.getDate() == 1) {
        console.log("Happy New Year!");
    }

})();

把上面这段代码放在全局作用域中,可以用来确定哪一天是1月1日。

如果到了这一天,就会向用户显示一条祝贺新年的信息。

其中的变量now现在是匿名函数中的局部变量,而我们不必在全局作用域中创建它。

这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。


来自卡冈图雅
33 声望1 粉丝