第7章 函数表达式

66 阅读4分钟

常见的函数表达式创建函数形式。

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

这种形式看起来好像是常规的变量赋值语句,即创建一个函数并将它赋值给变量 functionName。这种情况下创建的函数叫做匿名函数(anonymous function),因为 function 关键字后面没有标识符。(匿名函数有时候也叫拉姆达函数。)匿名函数的 name 属性是空字符串。

7.1 递归

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

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

arguments.callee 代替函数名

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

7.2 闭包

闭包是指有权访问另一个函数作用域中的变量的函数。

闭包的创建

创建闭包的常见方式,就是在一个函数内部创建另一个函数,并且内部函数引用外部函数的变量。

function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        alert(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();

7.2.1 闭包与变量

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

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

上述例子中,内部函数保存的外部函数整个变量对象,而外部的变量对象中的i只有一个,循环结束后,i为10;所以最终内部函数返回的i是最终循环后的10。

7.2.2 关于this对象

this 对象是在运行时基于函数的执行环境绑定的2

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};
alert(object.getNameFunc()()); //"The Window"

上述例子中,object.getNameFunc()调用后是返回了一个函数,我们假设返回的函数叫fn,那么object.getNameFunc()()相当于fn(),也就是相当于在全局环境调用返回的这个函数,所以this是指向window的。

7.2.3 内存泄漏

如果闭包的作用域链中保存着一个HTML 元素,那么就意味着该元素将无法被销毁。

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

    element.onclick = function(){
        alert(element.id);
    };
}

以上代码创建了一个作为 element 元素事件处理程序的闭包,而这个闭包则又创建了一个循环引 用(事件将在第 13 章讨论)。由于匿名函数保存了一个对 assignHandler()的活动对象的引用,因此 就会导致无法减少 element 的引用数。只要匿名函数存在,element 的引用数至少也是 1,因此它所 占用的内存就永远不会被回收。因此我们要手动把元素赋值为null。

function assignHandler(){
    var element = document.getElementById("someElement");
    var id = element.id;
    element.onclick = function(){
        alert(id);
    };
    element = null;
}

必须要记住:闭包会引用包含函数的整个活动对象,而其中包含着 element。即使闭包不直接引用 element,包含函数的活动对象中也仍然会保存一个引用。因此,有必要把 element 变量设置为 null。

7.3 模仿块级作用域

截至到ES5,Javascript没有块级作用域的概念。我们可以用IIFE来模仿一个块级作用域(通常称为私有作用域)

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

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

7.4 私有变量

任何在函数中定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。

function add(num1, num2){
    var sum = num1 + num2;
    return sum;
}

在这个函数内部,有 3 个私有变量:num1、num2 和 sum。

如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。而利用这一点,就可以创建用于访问私有变量的公有方法。我们把有权访问私有变量和私有函数的公有方法称为特权方法(privileged method)。

7.4.1 静态私有变量

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

(function(){

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

    //构造函数
    MyObject = function(){};

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

})();

7.4.2 模块模式

所谓的模块模式(module pattern)则是为单例创建私有变量和特权方法。所谓单例(singleton),指的就是只有一个实例的对象。按照惯例,JavaScript 是以对象字面量的方式来创建单例对象的

var singleton = {
    name : value,
    method : function () {
        //这里是方法的代码
    }
};