函数表达式

先来回顾一下,定义函数的方式有两种:一种是函数声明,一种是函数表达式。

//使用函数声明创建函数
function functionName(arg0,arg1,...,argN){
    //函数体
}

//使用函数表达式创建函数
var functionName = function(arg0,arg1,...,argN){
    //函数体
};

关于函数声明,有一个重要的特征就是函数声明提升,即在执行代码之前会先读取函数声明,这就意味着可以把函数声明放在调用它的语句后面。
而对于函数表达式而言,在使用之前必须先赋值。

这里就要谈到一个概念,即匿名函数
创建一个函数并将它赋值给一个变量,这种情况创建的函数叫做匿名函数,有时也叫拉达姆函数。
匿名函数的name属性是空字符串。
在把函数当成值来使用的情况下,都可以使用匿名函数。

一、递归

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

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

其中,arguments.callee是一个指向正在执行的函数的指针,可以用它来实现对函数的递归调用。使用arguments.callee代替函数名,在调用时不会出现问题。

二、闭包

1、闭包的概念

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

//函数内部可以直接读取全局变量
var a = 17;
function f1(){
    alert(a);
}
f1();  // 17

//在函数外部无法访问函数内的局部变量
function f2(){
    var b = 16;
}
alert(b);  // 出现错误!

function f3(){
    c = 15;
}
f3();
alert(c);  // 15

再看下面这个例子。

var result = function(){
    var n = 10;
    function func(){
        alert(n);
    }
    return func();
}
result();  // 10

我们知道,在JavaScript语言中,函数可以访问函数外部的变量,那么要想访问另一个函数的变量,只能是函数的子函数,这样的话,可以将闭包简单理解为定义在一个函数内部的函数。
在上面这个例子中,func函数就是闭包,而赋值给result的那个函数就是匿名函数。
由此可以知道,创建闭包的方式是:在一个函数内部创建另一个函数。

2、闭包与变量

闭包只能取得包含函数中任何变量的最后一个值。
闭包保存的是整个变量对象,而不是某个特殊的变量。

关于这两句话的理解可以看下面这个例子。

function createFunction(){
    var result = new Array();
    for (var i=0; i < 10; i++){
        result[i] = function(){
            return i;
        };
    }
    return result;
}
var func = createFunction();
for(var j=0; j < func.length;j++){
    document.write(func[j]() + "<br>");
}

输出结果为10个10。这是因为每个函数的作用域链中都保存着createFunction()函数的活动对象,所以它们引用的都是同一个变量i。当createFunction()函数返回后,变量i值是10,此时每个函数都引用着保存变量i的同一个变量对象。
如果希望位置0的函数返回0,位置1的函数返回1,则可以通过创建另一个匿名函数强制让闭包的行为符合预期。

function createFunction(){
    var result = new Array();
    for (var i=0; i < 10; i++){
        result[i] = function(num){
            return function(){
                return num;
            };
        }(i);
    }
    return result;
}
var func = createFunction();
for(var j=0; j < func.length;j++){
    document.write(func[j]() + "<br>");
}

在这里,我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。这里的匿名函数有一个参数num,也就是最终放入函数要返回的值。在调用每个匿名函数时,传入变量i,然后将变量i的值复制给参数num。而在这个匿名函数内部,又创建并返回了一个访问num的闭包。这样一来,result数组中的每个函数都有自己num变量的一个副本,就可以返回各自不同的数值了。

3、关于this对象

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

  • 在全局函数中,this等于window;
  • 当函数被作为某个对象的方法调用时,this等于那个对象。

匿名函数的执行环境具有全局性,所以this对象通常执行window,但是在通过call()或apply()改变函数执行环境的情况下,this就会指向其他对象。

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

在每个函数调用时,其活动对象都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,所以永远不可能直接访问外部函数中的这两个变量。但是,把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。

var name = "The Window";
var object = {
    name: "My Object",
    getName: function(){
        var that = this;
        return function(){
            return that.name;
        };
    }
};
alert(object.getName()());  // My Object
3、内存泄漏

一般来说,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是闭包的情况有所不同。在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。
具体来说,如果闭包的作用域保存着一个HTML元素,那么该元素将无法销毁。

4、使用闭包的注意点

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄漏。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,不要随便改变函数内部变量的值。

三、模仿块级作用域

1、JavaScript没有块级作用域的概念,这意味着在块语句中定义的变量,实际上是在包含函数中而非在语句中创建的。

2、JavaScript不会告诉你是否多次声明了同一个变量,它只会对后续声明视而不见,但是它会执行后续声明中的变量初始化。

3、匿名函数可以用来模仿块级作用域(私有作用域),语法如下:

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

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

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

上面这段代码会导致语法错误,是因为JavaScript将function关键字当作一个函数声明的开始,而函数声明后面不能跟圆括号。但是,函数表达式的后面可以跟圆括号。要将函数声明转换成函数表达式,只要加上一对圆括号即可。

【注】在匿名函数中定义的任何变量,都会在执行结束时被销毁。

4、用途与好处

一般来说,应该尽量少向全局作用域中添加变量和函数。而这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。

通过创建私有作用域,每个开发人员既可以使用自己的变量,又不必担心搞乱全局作用域。
这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就立即销毁其作用域链。

四、私有变量

JavaScript中没有私有成员的概念,所有对象属性都是公有的。但是有私有变量的概念。
私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。

1、特权方法:有权访问私有变量和私有函数的公有方法。

创建特权方法的方式:

1)在构造函数中定义特权方法——特权方法作为闭包有权访问在构造函数中定义的所有变量和函数。

function MyObject(){
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //特权方法
    this.publicMethod = function (){
        privateVariable++;
        return privateFunction();
    };
}

2)在私有作用域中定义私有变量和函数。

(function(){
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //构造函数
    MyObject = function(){
    };
    //公有/特权方法
    MyObject.prototype.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    };
})();

这个模式与构造函数中定义特权方法的主要区别在于,私有变量和函数是有实例共享的。

2、模块模式:为单例创建私有变量和特权方法。

单例,指的是只有一个实例的对象。

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

模块模式通过为单例添加私有变量和特权方法能够使其得到增强,其语法形式如下:

var singleton = function(){
    //私有变量和私有函数
    var privateFunction(){
        return false;
    }
    //特权/公有方法和属性
    return {
        publicProperty: true,
        publicMethod: function(){
            privateVariable++;
            return privateFunction();
        }
    };
}();
3、增强的模块模式
  • 在返回对象之前加入对其增强的代码。
  • 适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值