函数表达式

来源:互联网 发布:昊海软件 编辑:程序博客网 时间:2024/04/20 14:45

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

//使用函数声明创建函数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、增强的模块模式

  • 在返回对象之前加入对其增强的代码。
  • 适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况。
原创粉丝点击