《JavaScript高级程序设计》学习笔记(第七章)

函数表达式是JavaScript当中一个既强大又使人困惑的特性,特别是其中涉及到的闭包,更是令许多的初学者困惑不已。前端

在以前的章节中有介绍过,定义函数的方法有两种:一种是函数声明。数组

function functionName(arg0, arg1, arg2) {
        //函数体
    }

对于函数声明,有一个重要的特征就是能够把函数声明放在调用它的语句后面,由于解释器会在执行语句以前先读取函数的声明。闭包

另外一种方法是使用函数表达式。函数

var functionName = function(arg0, arg1, arg2){
        //函数体
    };

函数表达式与函数声明的一个主要不一样就是,它必须在定义以后才能使用,不然将会报错。函数表达式中的函数是一个匿名函数,即function关键字后面没有标识符。既然能够把函数赋值给变量,也就能够把函数做为其它函数的返回值。this

递归

JavaScript中的递归有个问题,将保存函数的变量赋值给另外一个变量时,由于函数名称改变了,因此在递归调用的时候会出现问题:prototype

function factorial(num){
        if (num <= 1){
            return 1;
        } else {
            return num * factorial(num-1);
        }
    }

    var anotherFactorial = factorial;
    factorial = null;
    alert(anotherFactorial(4)); //出错!

要解决这个问题,可使用arguments.callee。这个属性是一个指向正在执行的函数的指针,因此能够利用它来代替函数名,这就确保递归调用时函数名称改变也不会出错。可是,在严格模式下,使用argments.callee会致使错误。咱们可使用命名函数来达成相同的结果:指针

var factorial = (function f(num){
        if (num <= 1){
            return 1;
        } else {
            return num * f(num-1);
        }
    });

闭包

闭包是一个容易使人困惑的概念。闭包是指有权访问另外一个函数做用域中的变量的函数。建立装饰的常见方式就是嵌套定义函数。code

咱们在第4章的时候了解过做用域链。而对于做用域链清晰地理解,是理解闭包的重要关键。对象

当调用函数的时候,会为函数建立一个执行环境,并将该环境的活动对象加入到做用域链的前端。做用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。递归

当在一个函数内部定义另外一个函数的时候,外部函数的活动对象会被添加到内部函数的做用域链中。通常状况下,当函数执行完毕后,它的活动对象就会被销毁。可是,当有内部函数与其构成闭包时,由于内部函数的做用域链仍在引用外部函数的活动对象,所以只有当内部的函数也被销毁后,这个外部活动对象才会被销毁。

闭包与变量

闭包只能取得包含函数(即外部函数)中任何变量的最后一个值。

function createFunctions(){
        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都是10。可使用另外一个匿名函数来修正这个问题:

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

关于this对象

this对象是在运行时,基于函数的执行环境绑定的。可是,匿名函数的执行环境具备全局性,所以其this对象一般指向window

var name = "The Window";
    var object = {
        name : "My Object",
        getNameFunc : function(){
            return function(){
                return this.name;
            };
        }
    };
    alert(object.getNameFunc()()); //"The Window"(在非严格模式下)

这是由于每一个函数都有本身的this,因此当内部函数在搜索这个变量时,只会搜索到其活动对象为止,所以永远不可能直接访问到外部函数中的this

模仿块级做用域

以前讲过,在JavaScript当中没有块级做用域。可是,咱们可使用匿名函数来模仿块级做用域。

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

在函数的声明包含在一对圆括号中,表示它其实是一个函数表达式,而紧随其后的另外一对圆括号会当即调用这个函数。

在须要用到块级做用域的时候,就能够这样使用:

function outputNumbers(count){
        (function () {
            for (var i=0; i < count; i++){
                alert(i);
            }
        })();

        alert(i); //致使一个错误!
    }

私有变量

从技术角度来说,JavsScript中是没有私有成员的概念的,全部对象属性都是公开的。因可是对于函数而言,函数里面的变量对外部都是私有的,咱们能够利用在函数里面建立一个闭包,来建立用于访问函数内部私有变量的公有方法。

对于有权访问私有变量和私有函数的公有方法,咱们称之为特权方法(privileged method)。建立特权方法的方式有两种:一是在构造函数当中定义特权方法。

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

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

对于这个例子而言,变量privateVariable和函数privateFunction()只能经过特权方法publicMethod()来访问,咱们没法直接访问到内部私有的变量。
使用模式的缺点是针对每一个实例都会建立一样一组新方法,可使用另外一种方法,静态私有变量来避免这个问题。

静态私有变量

经过在私有做用域中定义私有变量和函数,也能够建立特权方法:

(function(){
        //私有变量和私有函数
        var privateVariable = 10;

        function privateFunction(){
            return false;
        }

        //构造函数
        MyObject = function(){
        };
        //公有/特权方法
        MyObject.prototype.publicMethod = function(){
            privateVariable++;
            return privateFunction();
        };
    })();

这个模式使用了函数表达式来定义特权方法,由于函数声明只能建立局部的函数,一样地,对于MyObject咱们也没有使用var关键字,这样可使其成为一个全局变量。

这个模式与构造函数模式的主要区别在于,私有变量和函数是由实例共享的。因为特权方法是在原型上定义的,所以全部实例都使用同一个函数。而这个特权方法做为一个装饰,问题保存着对包含做用域的引用。

模块模式

模块模式是为单例建立私有变量和特权的方法。所谓单例,即只有一个实例的对象。

通常状况下,JavaScript是以对象字面量的方式来建立单例对象的。

模块模式经过为单例添加私有变量和特权方法可以使其获得加强,其语法形式以下:

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

        function privateFunction(){
            return false;
        }

        //特权/公有方法和属性
        return {
            publicProperty: true,
            publicMethod : function(){
                privateVariable++;
                return privateFunction();
            }
        };
    }();

若是必须建立一个对象并以某些数据对其进行初始化,同时还要公开一些可以访问这些私有数据的方法,那么就可使用模块模式。

加强的模块模式

这个模式即在返回对象以前加入对其加强的代码。这种加强的模块模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性或方法对其加以增长的状况。

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;
    }();

小结

这章主要讨论了JavaScript当中的函数表达式与闭包。理解闭包的一个重要基础就是要透彻理解执行环境和做用域链。

函数表达式和闭包都是极其有用的特性,利用它们能够实现不少功能。不过,由于建立闭包必须维护额外的做用域,因此过分使用它们可能会占用大量内存。

相关文章
相关标签/搜索