函数表达式,Web前端之家

块级作用域是指在由花括号包裹的代码块中的作用域。在JavaScript中是没有块作用域的。为了理解这个概念,来看下面的例子:

《JavaScript高级程序设计》 Chapter 7: 函数表达式

  • 函数表达式是JavaScript中的一个既强大又容易令人困惑的特性。定义函数的方式有两种:一种是函数声明,另一种就是函数表达式。函数声明的语法是这样的。

function functionName (arg0, arg1, arg2) {
//函数体
}
//这里首先是function关键字,然后时函数的名字,这就是指定函数名的方式。
  • 有的浏览器给函数定义了一个非标准的name属性,即通过这个属性可以访问到给函数指定的名字。

alert(functionName.name); //“functionName”
  • 关于函数声明,它的重要特征就是函数声明提升(function declaration
    hoisting),意思是在执行代码之前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面。

sayHi();
function sayHi() {
    alert("Hi");
}
//这个例子不会抛出错误,因为代码会在执行之前先读取函数声明。
  • 第二种创建函数的方式是使用函数表达式,函数表达式有几种不同的语法形式。下面是最常见的一种。

var functionName = function (arg0, arg1, arg2) {
//函数体
}
  • 这种形式看起来就像是常规的变量赋值语句,即创建一个函数并将它赋值给变量functionName,这种情况下创建的函数叫做匿名函数,因为function关键字后面没有标识符。匿名函数的name属性是空字符串。
  • 函数表达式与其他表达式一样,在使用前必须先赋值,以下代码会导致错误。

sayHi();//错误,函数不存在
var sayHi = function() {
    alert("sayHi");
}
  • 下面的代码将会由于函数声明和函数表达式的不同而造成错误。

//不要这样做
if(condition) {
    function sayHi(){
        alert("hi"));
    }
} else {
    function sayHi() {
        alert("Yo");
    }
}
//这里会出现不同的浏览器有不同的结果。函数声明的重要特征就是函数声明的提升。
  • 下面使用函数表达式就不会又任何问题:

var sayHi;
if(condition){
    sayHi = function (){
        alert("hi");
    };
} else {
    sayHi = function(){
        alert("Yo!");
    };
}
  • 能够创建函数再赋值给变量,也就能够把函数作为其他函数的返回值返回。
for{......}alert;//结果会输出10

7.1 递归

  • 递归函数是在一个函数通过名字调用自身的情况下构成的,如下所示

function factorial(num) {
    if(num<=1) {
        return 1;
    } else {
        return num*factorial(num-1);
    }
}
  • 上面是一个经典的递归阶乘函数。虽然这个函数表面看起来没什么问题,但下面的代码却可能导致它出错。

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial(4));  //出错!
  • 以上代码先把factorial()函数保存在变量anotherFactorial中,然后将factorial变量设置为null,结果指向原始函数的引用只剩一个。但接下来调用anotherFactorial()时,由于必定调用factorial(),而factorial已经不再是函数,所以必定导致函数,在这种情况下,使用arguments.callee可以解决这个问题。
  • 我们知道,arguments.callee是一个指向正在执行的函数的指针,因此可以用它来实现对函数的递归调用,例如:

function factorial(num) {
    if(num<=1) {
        return 1;
    } else {
        return num*arguments.callee(num-1);
        //通过使用arguments.callee代替函数名,可以确保无论怎么样调用函数都不会出问题。因此,在编写递归函数时,使用arguments.callee总比使用函数名更保险。
    }
}
  • 在严格模式下,不能通过脚本访问arguments.callee,访问这个属性会导致错误。不过,可以使用命名函数表达式达成相同的结果,例如:

var factorial = (function f(num) {
    if(num<=1) {
        return 1;
    } else {
        return num*f(num-1);
    }
})
  • 以上代码创建了一个名为f()的命名函数表达式,然后将它赋值给变量factorial。即便把函数赋值给了另一个变量,函数名f仍然有效,所以递归函数照样能正确完成,这种方式在严格模式和非严格模式都行得通。

上面的代码中,我们在for循环中定义了变量i,在C++和Java等编程语言中,循环执行结束之后,for循环中的i变量会立刻被垃圾回收。但是在JavaScript中,不管是使用循环或某些判断之后,变量会一直存在。我们可以从打印结果中看到,for循环结束之后打印出的值是10。

7.2 闭包

  • 有不少开发人员总是搞不清匿名函数和闭包这两个概念,因此经常混用。闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数,仍以前面createComparisonFunction()函数为例:

function createComparisonFunction(propertyName) {
    return function(object1, object2) {
        var value1 = object1[propertyName];
        var value2 = object2[propertyName];
        //这两行代码是内部函数中的代码,这两行访问了外部函数中的变量propertyName.
        if(value1<value2) {
            return -1;
        } else if(value1>value2) {
            return 1;
        } else {
            return 0;
        )
    }
}
  • 即使所标注的这个内部函数被返回了,而且是在其他地方被调用了,但它仍然可以访问到变量propertyName。之所以还能访问这个变量,是因为内部函数的作用域链中包含createComparisonFunction()的作用域。
  • 第四章,介绍了作用链的概念,而有关如何创建作用域链以及作用域链有什么作用的细节,对理解闭包至关重要。当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后,使用arguments和其他命名参数的值来初始化函数的活动对象,但在作用域链中,外部函数的活动兑现始终处于第二位,外部函数的外部函数的活动对象处于第三位….直至作为作用域链终点的全局执行环境。
  • 在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。来看下面的例子:

function compare(value1, value2) {
    if(value1<value2) { 
        return -1;
    } else {
        return 1;
    } else {
        return 0;
    }
}
var result = compare(5, 10);
  • 以上代码先定义了compare()函数,然后又在全局作用域中调用了它,当调用compare()时,会创建一个包含arguments,
    value1,
    value2的活动对象,全局执行环境的变量对象在compare()执行环境的作用域链中则处于第二位。
  • 后台的每个执行环境都有一个表示变量的对象——变量对象。全局环境的变量对象始终存在,而像compare()函数这样的局部环境的变量对象,则只在函数执行的过程中存在。在创建compare()函数时,会先创建一个预先包含全局变量的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。当调用compare()函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的队形构建起执行环境的作用域链。显然,作用域链的本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
  • 一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域。但是闭包的情况又有所不同。
  • 在另一个函数内部定义的函数将会包含函数(即外部函数)的活动对象添加到它的作用域链中,因此,在createComparisonFunction()函数内部定义的匿名函数的作用链中,实际上将会包含外部函数createComparisonFunction()的活动对象。

var compare = createComparisonFunction("name");
var result = compare({name:"Nicholas"}, {name:"Greg"});
  • 这里当createComparisonFunction()函数在执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。换句话说,当createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中;直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁。

//创建函数
var compareName = createComparisonFunction("name");
//调用函数
var result = compareName({name: "Nicholas"}, {name: "Greg"});
//解除对匿名函数的引用
compareName = null;
  • [ ] 7.2.1 闭包与变量

  • 作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。

function createFunction() {
    var result = new Array();

    for(var i=0; i<10; i++) {
        result[i] = function (){
            return i;
        }
    }
    return result;
}
  • 实际上,每个函数都会返回10,因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的都是同一个变量i。当createFunctions()函数返回后,变量i的值是10,此时每个函数都引用这保存变量i的同一个变量对象,所以在每个函数内部i的值都是10。
  • 但是我们可以通过创建另一个匿名函数强制让闭包的行为符合预期。

function createFunctions() {
    var result = new Array();

    for(var i=0; i<10; i++) {
        result[i] = function(num) {
            return function() {
                return num;
            };
        }(i);
    }
    result result;
}
  • 在这个版本中,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行匿名函数的结果赋值给数组。这里的匿名函数有一个参数num,也就是最终的函数要返回的值,在调用每个匿名函数时,我们传入了变量i。由于函数参数是按值传递的,所以就会将变量i的当前值赋值给参数num,而在这个匿名函数的内部,又创建并返回一个访问num的闭包。这样一来,result数组中的每个函数都有自己num变量的一个副本,因此就可以返回各自不同的数值了。

  • [ ] 7.2.2 关于this对象

  • 在闭包中使用this对象可能会导致一些问题。我们知道,this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象,不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window,但有时候写闭包的方式不同,这一点可能不会那么明显。

var name = "the window";
var object = {
    name: "My Object",
    getNameFunc: function() {
        return function(){
            return this.name;
        }
    }

};
alert(object.getNameFunc()());
  • 以上代码先创建了一个全局变量name,又创建了一个包含name属性的对象,这个对象还包含了一个方法——getNameFunc(),它返回了一个匿名函数,而匿名函数又返回this.name。由于getNameFunc()返回一个函数。因此调用object.getNameFunc()()就会立即调用它返回的函数,结果就是返回一个字符串。然而,这个例子返回的字符串是”the
    window”,即全局name变量的值。为什么匿名函数没有取得其包含作用域的的this对象呢?
  • 前面曾经提到过,每个函数在被调用时都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问到外部函数中的这两个变量。不过,把外部作用域中的this对象保存在一个闭包能够访问到的变量里,即可以让闭包访问该对象了。

var name = "The window";
var object = {
    name: "My Object";

    getNameFunc: function() {
        var that = this;
        return function() {
            return that.name;
        }
    }
};
alert(object.getNameFunc()());//"My Object"
  • 在定义匿名函数之前,我们把this对象赋值给了一个名叫that的变量,而在定义了闭包之后,闭包也可以访问这个变量,因为它是我们在包含函数中特意声明的一个变量。即使在函数返回之后,that也仍然引用这object,所以调用object.getNameFunc()()就返回了”My
    Object”.
  • this和arguments也存在同样的问题。如果想访问作用域中的arguments对象,必须将对该对象的引用保存在另一个闭包能够访问的变量中。
  • 在几种特殊情况下,this值可能会意外的改变,比如,下面的代码是修改前面例子的结果。

var name = "The Window";
var object = {
    name: "My Object";

    getName: function () {
        return this.name;
    }
}
object.getName();//My object
(object.getName)();//My object
(object.getName=object.getName)();//The Window
  • 这里的第一行代码跟平常一样调用了object.getName(),返回的是“My
    Object”,
    因为this.name就是object.name。第二行代码在调用这个方法前先给他加上了括号。虽然加上括号之后,就好像只是引用一个函数,但this的值得到了维持,因为object.getName和(object.getName)的定义是相同的。第三行代码先执行了一条赋值语句,然后再调用赋值后的结果,因为这个赋值表达式的值是函数本身,所以this的值不能得到维持,结果就返回了”The
    Window”.

  • [ ] 7.2.3 内存泄漏

  • 闭包在IE的这些版本会导致一些特殊的问题。具体来说,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁。

function assignHandler() {
    var element = document.getElementById("someElement");
    element.onclick = function() {
        alert(element.id);
    };
}
  • 以上代码创建了一个作为element元素处理程序的闭包,而这个闭包则又创建了一个循环引用,由于匿名函数保存了一个对assignHandler()的活动对象的引用,因为就导致无法减少element的引用数。只要匿名函数存在,element的引用数至少也是1,因为它所占用的内存就永远不会被回收。
  • 这个问题可以通过稍微改一下代码来解决:

function assignHanlder() {
    var element = document.getElementById("someElement");
    var id = element.id;

    element.onclick = function() {
        alert(id);
    };
    element = null;
}
  • 在上面的代码中,通过把element.id的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用。但仅仅做到这一步,还是不能解释内存的问题。必须要记住:闭包会引用包含函数的整个活动对象,而其中包含着element。即使闭包不直接引用element,包含函数的活动对象中也仍然会保存一个引用。因此有必要把element变量设置为null。

当在全局环境中使用某个变量进行循环或判断之后,这个变量可能会影响到函数中的变量,所以在非特殊情况下不要使用全局变量,而且全局变量在作用域链的最上层,访问是最慢的。

7.3 模仿块级作用域

  • 如前所述,JavaScript没有块级作用域,这意味着在块语句中定义的变量,实际上是包含函数中而非语句中创建的。

function outputNumbers(count) {
    for (var i=0; i<cout; i++) {
        alert(i);
    }
    alert(i); //计数
}
  • 这个函数中定义了一个for循环,而变量i的初始值被设置为0。在JavaScript中,变量i是定义在outputNumbers()的活动对象中的,因此从它有定义开始,就可以在函数内部随处访问他,即使下面这样错误的重新声明同一个变量,也不会改变他的值。

function outputNumbers(count) {
    for(var i=0;i<count; i++) {
        alert(i);
    }
    var i;//重新声明变量
    alert(i);//计数
}
  • JavaScript从来不会告诉你是否多次声明同一个变量,遇到这种情况,它只会对后续的声明视而不见(不过,它会执行后续声明中的变量初始化)。匿名函数可以用来模仿块级作用域并且避免这个问题。
  • 用块级作用域(通常称为私有作用域)的匿名函数的语法如下:

(function() {
    //这里是块级作用域
})();
  • 以上代码定义并且立即调用了一个匿名函数。将函数声明包含在一对括号中,表示它实际上是一个函数表达式,而紧随其后的另一对圆括号会立即调用这个函数。

var count = 5;
outputNumbers(count);
  • 这里初始化了变量count,将其值设置为5,当然,这里的变量是没有必要的。因为可以把值直接传给函数,为了让代码更简洁,我们在调用函数时用5来代替变量count。

outputNumbers(5);
  • 这样做之所以可行,是因为变量只不过是值的另一种变现形式。因此用实际的值替换变量没有问题。

var someFunction = function() {
    //这里是块级作用域
}
someFunction();
  • 这个例子先定义了一个函数,然后立即调用了它。定义函数的方式是创建了一个匿名函数,并把匿名函数赋值给变量someFunction。而调用函数的方式是在函数名称后面添加一对圆括号,即someFunction()。通过前面的例子我们知道,可以使用实际的值来取代变量count。
  • 下面这个例子将会导致错误:

function() {
//这里是块级作用域
}();//出错!
  • 这段代码会导致语法错误,是因为JavaScript将function关键字当做一个函数声明的开始,而函数声明的后面不能跟圆括号。然而,函数表达式的后面可以跟圆括号。要将函数声明转换成函数表达式,只要像下面这样给它加上一堆圆括号即可。

(function() {
    //这里是块级作用域
})();
  • 无论在什么地方,只要临时需要一些变量,就可以使用私有作用域,例如:

function outputNumbers(count) {
    (function () {
        for(var i=0; i<count; i++) {
            alert(i);
        }
    })();
    alert(i);//导致一个错误
}
  • 这个函数中,我们在for循环外部插入一个私有作用域。在匿名函数中定义的任何变量,都会在执行函数结束的时候被销毁。因此,变量i只能在循环中使用,使用后即被销毁,而在私有作用域中能够访问的变量count,是因为这个匿名函数是一个闭包,它能够访问包含作用域中的所有变量。
  • 这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。一般来说,我们都应该尽量少向全局作用域中添加变量和函数。

解决块作用域的方法是使用匿名函数。来看下面的代码。

7.4 私有变量

  • 严格来讲,JavaScript中没有私有成员的概念,所有对象属性都是公有的。不过,倒是有一个私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数外部访问这些变量。私有变量包含函数的参数,局部变量和在函数内部定义的其他函数。

function add(num1, num2) {
    var sum = num1+num2;
    return sum;
}
  • 在这个函数内部,有3个私有变量: num1, num2,
    sum。在函数内部可以访问这几个变量,但在函数外部则不能访问它们。如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量,而利用这一点,就可以创建用于访问私有变量的公有方法。
  • 我们把有权访问私有变量和私有函数的公有方法称为特权方法(pribileged
    method)。有两种对象上创建特权方法的方式。第一种是在构造函数中定义特权方法,基本模式如下:

function MyObject() {
//私有变量和私有函数
    var privateVariable = 10;
    function privateFunction() {
        return false;
    }
    //特权方法
    this.publicMethod = function() {
        privateVariable++;
        return privateFunction();
    };
}
  • 这个模式在构造函数内部定义了所有私有变量和函数。然后,又继续创建了能够访问这些私有成员的特权方法。能够在构造函数中定义特权方法,是因为特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。在创建MyObject的实例后,除了使用publicMethod()这一个途径中,没有任何办法可以直接访问privateVariable和privateFunction()。

function Person(name) {
    this.getName = function() {
        return name;
    };
    this.setName = function(value) {
        name = value;
    };
    var person = new Person("Nicholas");
    alert(person.getName());//"Nicholas"
    person.setName("Greg");
    alert(person.getName());//"Greg"
}
  • 以上代码的构造函数中定义了两个特权方法:
    getName()和setName()。这两个方法都可以在构造函数外部使用,而且都有权访问私有变量name。但在Person构造函数外部,没有任何方法访问name。由于这两个方法是在构造函数内部定义的,它们作为闭包能够通过作用域链访问name。私有变量name在Person的每一个实例中都不相同,因为每次调用构造函数内部定义的,它们作为闭包能够通过作用域链访问name。私有变量name在Person的每一个实例中都不相同,因为每次调用构造函数都会重新创建这两个方法。
  • 不过在构造函数中定义特权方法也有一个缺点,那就是你必须使用构造函数来达到这个目的。

  • [ ] 7.4.1 静态私有变量

  • 通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法,其基本模式如下:

(function () {
//私有变量和私有函数
    var privateVariable = 10;
    function privateFunction() {
        return false;
    }
    //构造函数
    MyObject = function() {
    };
    //公有/特权方法
    MyObject.prototype.publicMethod = function() {
        privateVariable++;
        return privateFunction();
    };
})();
  • 这个模式创建了一个私有作用域,并在其中封装了一个构造函数及相应的方法。在私有作用域中,首先定义了私有变量和私有函数,然后又定义了构造函数及其公有方法。公有方法是在原型上定义的,这一点体现了典型的原型模式。需要注意的是,这个模式在定义构造函数时并没有使用函数声明,而是使用了函数表达式。函数声明只能创建局部函数,但那并不是我们想要的。处于同样的原因,我们也没有在声明MyObject时使用var关键字。记住:初始化未经声明的变量,总是会创建一个全局变量。因此,MyObject就成了全局变量,能够在私有作用域之外被访问到。但也要知道,在严格模式下给未经声明的变量复制会导致错误。
  • 这个模式与在构造函数中定义特权方法的主要区别,就在于私有变量和函数是有实例共享的。由于特权方法是在原型上定义的,因此所有实例都使用同一个函数,而这个特权方法,作为一个闭包,总是保存着对包含作用域的引用。

(function(){
    var name = "";
    Person = function(value){
        name = value;
    };
    Person.prototype.getName = function(){
        return name;
    };
    Person.prototype.setName = function (value){
        name = value;
    };
})();
var person1 = new Person("Nicholas");
alert(person1.getName()); //"Nicholas"
person1.setName("Greg");
alert(person1.getName()); //"Greg"
var person2 = new Person("Michael");
alert(person1.getName()); //"Michael"
alert(person2.getName()); //"Michael"
  • 这个例子中的Person构造函数与getName()和setName()方法一样,都有权访问私有变量name。在这种模式下,变量name就变成了一个静态的,由所有实例共享的属性。也就是说,在一个实例上调用setName()会影响所有实例。而调用setName()或新建一个Person实例都会赋予name属性一个新值。结果就是所有实例都会返回相同的值。
  • 以这种方式创建静态私有变量会因为使用原型而增加代码的复用,但每个实例都没有自己的私有变量,到底是使用实例变量还是静态私有变量,最终视情况而定。

  • [ ] 7.4.2 模块模式

  • 前面的模式是用于为自定义类型创建私有变量和特权方法的。而这里所说的模块模式则是为单例创建私有变量和特权方法。所谓单例,指的就是只有一个实例的对象。按照惯例,js是以对象字面量的方式来创建单例对象的。

var singleton = {
    name: value,
    method: function() {
    //这里是方法的代码
    }
}
  • 模块模式通过为单例添加私有变量和特权方法能够使其得到增强,其语法形式如下:

var singleton = function (){
//私有变量和私有函数
    var privateVariable = 10;
    function privateFunction() {
        return false;
    }

    //特权或者公有方法和属性
    return {
        publicProperty: true;
        publicMethod: function() {
            privateVariable++;
            return privateFunction();
        }
    };
}();
  • 这种模块模式使用了一个返回对象的匿名函数,在这个匿名函数的内部,首先定义了私有变量和函数,然后,将一个对象字面量作为函数的值返回,返回的对象字面量中只包含可以公开的属性和方法。由于这个对象是在匿名函数内部定义的,因此它的公有方法有权访问私有变量和函数,从本质上讲,这个对象字面量定义的是单例的公共接口,这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的。

var application = function() {
    //私有变量和函数
    var components = new Array();
    //初始化
    components.push(new BaseComponent());
    //公共
    return {
        getComponentCount: function (){
            return components.length();
        }, 
        registerComponent: function (component) {
            if(typeof component == "object") {
                components.push(component);
            }
        }
    };
}
  • 这个简单的例子创建了一个用于管理组件的application对象,在创建这个对象的过程中,首先声明了一个私有的components数组,并向数组中添加了一个BaseComponent的新实例。而返回对象的getComponentCount()和registerComponent()方法,都是有权访问数组components的特权方法。前者只是返回已注册的组件数目,后者用于注册新组件。
  • 以这种模式创建的每个单例都是Object的实例,因为最终要通过一个对象字面量来表示它。

  • [ ] 7.4.3 增强的模块模式

  • 有人进一步改进了这种模式,即在返回对象之前加入对其增强的代码。这种代码的模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性和方法对其加以增强的情况。

var singleton = function(){
//私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //创建对象
    var object = new CustomType();
    //添加特权/公有属性和方法
    object.publicProperty = true;
    object.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    };
    //返回这个对象
    return object;
}();
  • 如果前面演示模块的例子中的application对象必须是BaseComponent的实例,那么可以使用一下代码:

var application = function(){
    //私有变量和函数
    var components = new Array();
    //初始化
    components.push(new BaseComponent());
    //创建 application 的一个局部副本
    var app = new BaseComponent();
    //公共接口
    app.getComponentCount = function(){
        return components.length;
    };
    app.registerComponent = function(component){
        if (typeof component == "object"){
            components.push(component);
        }
    };
//返回这个副本
    return app;
}();
  • 在这个重写后的应用程序单例中,首先是像前面例子中一样定义了私有变量。主要的不同之处在于命名变量app的创建过程,因为它必须是BaseComponent的实例,这个实例实际上是application对象的局部变量版。此后,我们又为app对象添加了能够访问私有变量的公有方法。最后一步是返回app对象,结果仍然是将它赋值给全局变量application.
{for{......}})();//直接打印i值会报错:i没有定义alert;//iisnotdefinedfunctionfn;

在上面的代码中,我们通过将代码块放入一个匿名函数中,然后马上调用了这个匿名函数。注意到匿名函数之后的一对括号,它表示调用匿名函数。你可以在很多JavaScript程序中看到这种写法。此时,在匿名函数中的变量在使用完之后就会被回收,在匿名函数外部是访问不到这些变量的。

在我们进行团队开发时,可能会涉及到定义同名的全局变量,所以在开发中我们要养成如下的习惯:将全局变量的代码放入到一个匿名函数中,并且马上调用匿名函数,这样也可以执行全局变量的代码,但是这些变量就被控制在我们想要控制的作用域中了。

私有变量

我们前面在定义一个对象的时候,通过this关键字来设置对象的属性的。通过这种方法设置的属性我们称为公共属性,我们可以通过对象来直接访问这些属性。

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图