JavaScript.The.Good.Parts阅读笔记(二)作用域&闭包&减缓全局空间污染

块级作用域: 大多数使用c语言语法的语言都有块级作用域,而JavaScript没有块级作用域

如代码块

if (true) {

int i = 100; 

print(i); //错误,变量i没有声明

如上面例子所示,代码块外的函数是无法访问i变量的。

但在javaScript里,情况则完全不同。

if (true) {

var i = 100; 

alert(i); //弹出框并显示100  

很多现代语言都推荐尽可能迟地声明变量,但在Javascript里这是一个最糟糕的建议。由于缺少块级作用域,最好在函数体的顶部声明函数中可能用到的所有变量。


闭包特性

虽然缺少块级作用域,但是函数的作用域还是存在的。

这种作用域有一个好处,就是内部函数可以访问定义它们的外部函数的参数和变量(除了this和argument)。

利用这种特性,则可以这样来设计代码。

 

    var bankAccount = function () {

        var value = 0;

        return {

            deposit: function (inc) {

                value += inc;

            },

            getValue: function (){

                return value;

            }

        }

    }


    var myAccount = bankAccount();        //新开一个银行账户

    myAccount.deposit(1000);                 //存1000块进去

    alert(myAccount.getValue());              //should alert(1000); 

 

value由于在bankAccount这个function里,外部无法对它进行直接操作,必须通过bankAccount function给他返回的对象来进行操作,通过这样来实现C#和java里的private的字段。

 

减缓全局变量污染全局空间:利用函数的作用域,我们在写js库的时候可以减少跟其他库冲突。

(function () {

var hello = 'Hello World.';

})();

  alert(hello);  //error: hello is not defined.

 

外部的alert访问不到在函数内部定义的hello。这样一个类库里面用到的临时变量就不会被别的库所篡改了。

 

请注意上面包住function的括号。

"Note that an ExpressionStatement cannot start with an opening curly brace because that might make it ambiguous with a Block. Also, an ExpressionStatement cannot start with the function keyword because that might make it ambiguous with a FunctionDeclaration."

出自于《ECMAScript-262》12.4 Expression Statement。主要意思是说function不能用在表达式的开头,否则会被误认为是一个FunctionDeclaration而不是一个FunctionExpression。

想更详细了解可以参考一下网址,介绍的很清楚:

http://dmitrysoshnikov.com/ecmascript/chapter-5-functions/

PS:这句话同样解释了eval('(' + JsonString + ')'); 为什么要加括号在两边。


陷阱一:var的陷阱

 

“减缓全局变量污染全局空间”的例子改成

(function () {

hello = 'Hello World.'; //remove var

})();

alert(hello);  //alert ('Hello World.');

当变量hello没有用var显式声明时,hello成为了一个全局变量!!

 

虽然利用这个特性,咱们可以提供一个对外接口,但不建议这样做。

 (function () {

var hello = 'Hello World.'; 

  sayHello = function () {   //不建议采用这种方式来提供接口,看起来很不明显。

alert(hello); 

})();

sayHello();

 

可以改进为

(function (window) {

var hello = 'Hello World.'; 

  window.$ = {

sayHello: function () {

alert(hello); 

};

})(window);

$.sayHello();  //看起来像jQuery那么酷

var obj = (function () {

var hello = 'Hello World.'; 

  return {

sayHello: function () {

alert(hello); 

};

})();

obj.sayHello(); 

 

陷阱二: 闭包的陷阱

 

(function () { //函数a

    var arr = [];

  var i = 0;

    var j; 

    for ( ; i < 3; i++) {

        arr.push(function () { //函数b

            alert(i * 10);

        });

    }


    for (j in arr) {

        arr[j]();

    }

})(); 

 

原以为函数数组arr里各个函数执行后,会弹出0,10,20,但是结果不是如此。结果是弹出30,30,30。
函数b访问的不是当时的 i的值, 而是直接访问变量i(用于都是取i最新的值)。
原因是函数b是函数a的内部函数,变量i对函数b是可见的,函数b每次都从i处获取最新的值。 

 

这次改成: 

(function () { //函数a

    var arr = [];

    var i = 0; 

  var j; 

    for ( ; i < 3; i++) {

        arr.push((function (anotherI) { //函数m

            return function () { //函数b

                alert(anotherI * 10);

            }

        })(i)); // 此处为(function b(anotherI) {})(i)

    }


    for (j in arr) {

        arr[j]();

    }

})();

 

这次执行后,终于弹出0,10,20。这是为什么呢。

函数b访问的是anotherI(当时的i的值),而不是直接访问变量i。 
每次在arr.push前,都会定义一个新匿名的函数m。本例中定义了3个匿名函数m0,m1,m2,每当被调用后,他们的anotherI都得到当前i的值。每个m函数执行后都返回一个b函数。b0在m0里,b1在m1里,b2在m2里。b0只能访问m0的anotherI(为0),而b0访问不了m1的anotherI,因为m0和m1为不同的函数。
posted @ 2010-11-16 21:30  Stephen.Huang  阅读(377)  评论(0编辑  收藏  举报