函数表达式的特征

函数表达式和其他表达式一样,在使用前必须先赋值。

递归

    //递归阶乘函数
    function factorial(num) {
        if (num <= 1) {
            return 1;
        } else {
            return num * factorial(num - 1);
        }
    }

    //虽然这个函数表面看来没什么问题,但下面的代码却可能导致它出错。
    var anotherFactorial = factorial;
    factorial = null;
    // alert(anotherFactorial(4));//TypeError

    //可以用命名函数表达式避免
    var factorial = (function f(num) {
        if (num < 1) {
            return 1;
        } else {
            return num * f(num - 1);
        }
    });

    alert(anotherFactorial(4));//24

闭包

闭包是指有权访问另一个函数作用域中变量的函数。
创建闭包的常用方式是在一个函数内部创建另一个函数。

    function createComparisionFunction(propertyName) {

        return function (object1, object2) {
            //内部函数可以访问外部变量propertyName
            var value1 = object1[propertyName];
            var value2 = object2[propertyName];

            if (value1 < value2) {
                return -1;
            } else if (value1 > value2) {
                return 1;
            } else {
                return 0;
            }
        }
    }

    var compare = createComparisionFunction("name1");

    //在createComparisionFunction函数外部调用内部函数compare仍可访问变量propertyName
    compare();

当函数被调用时,会创建一个执行环境和相应的作用域链。

    function compare (value1, value2) {
        if (value1 < value2) {
            return -1;
        } else if (value1 > value2) {
            return 1;
        } else {
            return 0;
        }
    }

    var result = compare(5, 10);

以上代码先定义compare()函数,然后在全局作用域中调用了它。
1. 创建函数时,会创建一个预先包含全局变量对象的作用域链,并被保存到函数内部的[[Scope]]属性中。
2. 调用函数时,从[[Scope]]属性中复制作用域链,此后又有一个活动对象被推到作用域的前端。

compare在被调用时的作用域链

变量对象存储当前作用域中定义的变量和函数声明,每个执行环境中都有一个变量对象
活动对象在函数被调用时创建,包含形参和arguments
作用域链的前端始终是当前执行代码所在的环境的变量对象,如果这个环境是函数,则将其活动对象作为变量对象。
全局变量对象始终是作用域链中的最后一个对象。

在一个函数内部定义一个函数,内部函数将会把外部函数的活动对象加到其作用域链中。

    /*在匿名函数从createComparisionFunction中返回后, 匿名函数的作用域链被初始化为包含createComparisionFunction活动对象和全局变量对象, 这样,匿名函数就可以访问createComparisionFunction中的变量, createComparisionFunction()函数执行完毕后,createComparisionFunction()的活动对象不会被销毁,但是作用域链会被销毁。 */

    //创建函数
    var compareNames = createComparisionFunction("name");

    //调用函数
    var result = compareNames({ name: "Jack" }, { name: "Tom" });

在被调用compareNames时的作用域链

匿名函数被销毁后,createComparisionFunction的活动对象才会被销毁。

    //解除对匿名函数的引用
    compareNames = null;
闭包与变量

闭包只能取得外部函数中的变量的最终值。

    function createFunctions() {
        var result = new Array();
        for (var i = 0; i < 10; i++) {
            result[i] = function () {
                return i;
            };
        }

        //此时,i = 10
        return result;
    }

    var functions = createFunctions();
    for (var i = 0; i < 10; i++) {
        var temp = functions[i]();
        console.log(temp);
    }

    /**output 10 10 ... 10 */

通过一个创建一个匿名立即执行函数,返回一个可以访问匿名函数变量num的闭包。

    function createFunctions() {
        var result = new Array();
        for (var i = 0; i < 10; i++) {
            result[i] = function (num) {
                return function () {
                    return num;
                }
            }(i);
        }
        return result;
    }

    var functions = createFunctions();
    for (var i = 0; i < 10; i++) {
        var temp = functions[i]();
        console.log(temp);
    }

    /**output 0 1 ... 9 */
关于this对象

this是基于函数的执行环境绑定的,当函数被作为某个对象的方法调用时,this对象指向那个对象。

    var name = "the window"

    var object = {
        name: "my object",
        getNameFunc: function () {//此函数被object对象的方法调用,所以this指向object 
            console.log("getNameFunc " + this.name);
            return function () {//此匿名函数没有绑定到任何一个对象上,所以this指向window
                return this.name;
            }
        }
    }

    console.log(object.getNameFunc()());

    /*output getNameFunc my object the window */

在外部函数中定义一个that变量保存外部函数(getNameFunc)的this对象,通过闭包访问that变量来访问外部函数的this对象。

    var name = "the window"

    var object = {
        name: "my object",
        getNameFunc: function () {
            var that = this;
            return function () {
                return that.name;
            }
        }
    }

    console.log(object.getNameFunc()());//my object
模仿块级作用域
    //可以通过这种方式,创建一个私有作用域,匿名函数中定义的任何变量都在执行后被销毁
    //经常子在全局作用域中使用,从而限制向全局作用域中添加过多的变量和函数
    (function () {
        //do something
    })();
私有变量

所有对象的属性都是公有的
任何在函数中定义的变量都可以被看做私有变量。

在函数内部创建的闭包可以通过作用域链访问函数的私有变量。私有变量和私有函数的公有方法称为特权方法

    //在构造函数中定义特权方法
    function MyObject() {

        //私有变量和私有函数
        var privateVariable = 10;

        function privateFunction() {
            return false;
        }

        //特权方法
        this.publicMethod = function (){
            privateVariable++;
            return privateFunction();
        }
    }

静态私有变量

    (function() {
        var name = '';

        //没有使用var声明,是全局变量
        Person = function(value) {
            name = value;
        }

        Person.prototype.getName = function() {
            return name;
        };

        Person.prototype.setName = function(value) {
            name = value;
        };
    })();

    var person1 = new Person('Jack');
    alert(person1.getName()); //Jack
    person1.setName('Tom');
    alert(person1.getName()); //Tom

    var person2 = new Person('Jerry');

    //因为setName(), getName()在原型上定义的,被所有实例共享,在一个实例上调用会影响所有实例
    //所以私有变量可以看做是静态的。
    alert(person1.getName()); //Jerry
    alert(person2.getName()) //Jerry

模块模式

    //返回对象的匿名函数
    var singleton = function() {
        var privateVariable = 10;

        function privateFunction() {
            return false;
        }
        // 特权 / 公有方法和属性
        //返回包含可以访问私有变量和函数的对象字面量
        return {
            publicProperty: true,

            publicMethhod: function() {
                privateVariable++;
                privateFunction();
            }
        }
    }

如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。以这种模式创建的每个单例都是Object的实例,因为最终要通过一个对象字面量来表示它。

   /** * 在创建这个对象的过程中,首先声明了一个私有的components数组,并向数组中添加了一个BseComponent的新实例。 * 而返回对象的getComponentCount()和registerComponent()方法,都是有权访问数组components的特权方法。 */
   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对象必须是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;
    }();