函数表达式的用法

函数表达式

匿名函数(拉姆达函数)

创建一个函数并将它赋值给变量functionName

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

函数表达式与其他表达式一样,在使用前必须先赋值。(使用“函数声明”定义的函数有着函数声明的特征)

在把函数当成值来使用的情况下,都可以使用匿名函数


应用:

1. 递归

arguments.callee是一个指向正在执行的函数的指针,可用它来实现对函数的递归调用

1
2
3
4
5
6
7
function factorial(num){
if(num<=1){
return 1;
}else{
return num*arguments.callee(num-1);
}
}

但在==严格模式==下,不能通过脚本访问arguments.callee。访问这个属性会导致错误

解决方法:使用命名函数表达式来达成相同的结果

1
2
3
4
5
6
7
var factorial=(function f(num){
if(num<=1){
return 1;
}else {
return num * f(num-1);
}
});

以上代码创建了一个名为f()的命名函数表达式,然后将它赋值给变量factorial。即便把函数赋值给了另一个变量,函数的名字f仍然有效,所以递归调用照样能正确完成。


2. 闭包

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

无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)但是,闭包不同

在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function createComparisonFunction(propertyName){

return function(object1,object2){
var value1=object1[propertyName];
var value2=object2[propertyName];

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

//创建函数
var compare=createComparisonFunction("name");

//调用函数
var result=compare({ name:"Nicholas"},{ name:"Greg"});

在匿名函数从createComparisonFunction()中被返回后,它的作用域链被初始化为包含createComparisonFunction()函数的活动对象和全局变量对象。createComparisonFunction()函数在执行完毕后,其活动对象也不会销毁,因为匿名函数的作用域链仍然在引用这个活动对象。换句话说,当createComparisonFunction()返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会保留在内存中;直到匿名函数被销毁后,createComparisonFunction()的活动对象才会被销毁。()

==由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存==

  • 闭包与变量

    闭包所保存的是整个变量对象,而不是某个特殊的变量。同时闭包只能取得包含函数中的任何变量的最后一个值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function createFunctions(){
    var result=new Array();
    for(var i=0;i<10;i++){
    result[i]=function(){
    return i;
    };
    }
    return result;
    }

    var x=createFunctions();
    document.write(x[0]()); //结果为10 并非0

这个函数会返回一个函数数组。但是每个函数都返回10,而不是返回自己的索引值(位置0的函数返回0,位置1的函数返回1)

*原因:* 每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的都是同一个变量i。当createFunctions()函数返回后,变量i的值是10,此时每个函数都引用着保存变量i的同一个变量对象,所以在每个函数内部i的值都是10.

改善代码

1
2
3
4
5
6
7
8
9
10
11
function createFunctions(){
var result=new Array();
for(var i=0;i<10;i++){
result[i]=function(num){
return function(){
return num;
};
}(i);
}
return result;
}

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

  • 闭包中的this对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var name="The Window";
    var object={
    name:"My Object",

    getName:function(){
    return this.name;
    }
    };
    object.getName(); //"My Object"

    匿名函数的执行环境具有全局性,因此其this对象通常指向window

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var name="The Window";
    var object={
    name:"My Object",

    getNameFunc:function(){
    return function(){
    return this.name;
    };
    }
    };

    alert(object.getNameFunc()()); //"The Window"(在非严格模式下)

方法getNameFunc()返回一个匿名函数,而该匿名函数又返回this.name

结果解析:内部函数在搜索this变量时,只会搜索到其活动对象为止,因此永远不可能直接访问到外部函数的this变量(arguments变量也是如此)

把外部作用域中的this对象保存在一个闭包能够访问的变量里,就可以让闭包访问该对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
var name="The Window";
var object={
name:"My Object",

getNameFunc:function(){
var that=this;
return function(){
return that.name;
};
}
};

alert(object.getNameFunc()()); //"My Object"(在非严格模式下)


3. 模仿块级作用域(私有作用域)

众所周知,JavaScript没有块级作用域的概念
例如:

1
2
3
4
5
6
7
8
9
function outputNumbers(count){
for (var i=0;i<count;i++){
alert(i);
}

alert(i); //即使变量i是在for循环中定义的,但实际上是定义在outputNumbers()的活动对象中的

var i; //重复声明变量
}

JavaScript从来不会告诉你是否多次声明了同个变量;只会对后续的声明视而不见(但是会执行后续声明中的变量初始化)

匿名函数可以用来模仿块级作用域

1
2
3
4
var someFunction=function(){
// 这里是块级作用域
};
sonmeFunction();

1
2
3
4
5
6
7
8
9
//简约版

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

/*将函数声明包含在一对圆括号中,表明它实际上是一个函数表达式。
而紧跟其后的另一对圆括号会立即调用这个函数。
函数声明外面的那一堆圆括号不能省略,省略会出错*/

如果临时需要一些变量,就可以使用私有作用域。例如:

1
2
3
4
5
6
7
8
9
10
11
12
function outputNumbers(count){
(function(){
for(var i=0;i<count;i++){
alert(i);
}
})();

alert(i); //报错

}
/* outputNumbers()函数中,在for循环外部插入了一个私有作用域。
在匿名函数中定义的任何变量,都会在执行结束时蓓销毁。因此变量i只能在循环中使用*/

模仿私有作用域经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数


4. 私有变量

任何在函数中定义的变量,都可以认为是私有变量。

私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。

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

创建特权方法一:在构造函数中定义特权方法

==把函数设置成构造函数,通过构造函数创建对象,从而通过对象来访问私有变量。==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Person(name){
this.getName=function(){
return name;
}; //特权方法

this.setName=function(value){
name=value;
}; //特权方法
}
var person=new Person("yu");
alert(person.getName()); //"yu"

person.setName("Ryota");
alert(person.setName()); //"Ryota"

Person构造函数中有两个特权方法,这两个方法都可以在构造函数外部使用,而且都有权访问私有变量name。

注意:私有变量name在Person的每一个实例中都不相同,因为每次调用构造函数都会重新创建这两个方法。(这也是构造函数模式的缺点

创建特权方法二:静态私有变量

==通过在私有作用域中定义私有变量或函数,同样可以创建特权方法==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
(function(){
//私有变量
var name="";
//构造函数(使用函数表达式而不用函数声明 因为函数声明只能创建局部函数 )
//Person不使用var关键字 因为它得是一个全局变量
Person=function(value){
name=value;
};

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

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


//实例1
var person1=new Person("yu");
alert(person1.getName()); //"yu"
person1.setName("hjh");
alert(person1.getName()); //"hjh"
//实例2
var person2=new Person("Ryota");
alert(person1.getName()); //"Ryota"
alert(person2.getName()); //"Ryota"

精髓:变量name是一个静态的,由所有的实例共享的属性

也就是说,在一个实例上调用setName()会影响所有实例。而调用setName()或新建一个Person实例都会赋予name属性一个新值。