后端 函数表达式的用法

moononefly · June 12, 2020 · 6 hits

函数表达式

匿名函数(拉姆达函数)

创建一个函数并将它赋值给变量 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 属性一个新值。

No Reply at the moment.
You need to Sign in before reply, if you don't have an account, please Sign up first.