JavaScript高级程序设计笔记(4)-引用类型之Function

Function

函数实际上是对象,每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法。由于函数实际对象,因为函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。函数通常是使用函数声明语法定义的。

1
2
3
4
5
6
7
8
9
10
//函数声明语法
function sum(num1, num2){
return num1 + num2;
}
//函数表达式定义
var sum = function(num1, num2){
return num1 + num2;
}
//Function构造函数,不推荐
var sum = new Function('num1','num2','return num1 + num2');

由Function构造函数可以直观看出:函数是对象,函数名是指针。

没有重载

在javascript中,如果声明了两个同名函数,后面的函数会覆盖前面的函数。

tips:

重载,简单说,就是函数或者方法有相同的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。

函数声明与函数表达式

解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);函数表达式,则必须等到解析器执行到它所在的代码行,才会被真正解释执行。

1
2
3
4
alert(sum(10,10));
function sum(num1, num2){
return sum1 + sum2;
}

以上代码可正常执行,代码开始前,解析器已经通过一个函数声明提升的过程,读取并将函数声明添加到执行环境。对代码求值时,Javascript引擎在第一遍会声明函数并将它们放到源代码树的顶部。所以即使声明函数的代码在调用它的代码后面,Javascrit引擎也能把函数声明提升到顶部。
下面的代码将函数声明改为函数表达式,执行期间就会导致错误。

1
2
3
4
alert(sum(10,10));
var sum = function(num1, num2){
return sum1 + sum2;
}

上述代码产生错误的原因在于函数位于一个初始化语句中,而不是一个函数声明。在执行到函数所在语句之前,变量sum中不会保存有对函数的引用。而且由于第一行代码就会导致”unexpected identifier”错误,实际上也不会去执行到下一行。

除了通过变量访问函数的时间不同这一点除外,函数声明与函数表达式的语法其实是等价的。

作为值的函数

ECMAScript中的函数名本身就是变量,所以函数也可以作为值来使用。不仅可以像传递参数一样把一个函数传递给另一个函数,而且也可以将一个函数作为另一个函数的结果返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function callSomeFun(somFun, somArgs){
return somFun(somArgs);
}

function add10(num){
return num + 10;
}
var result1 = callSomeFunction(add10, 10);
alert(result1); //20
function getGreeting(name){
return "Hello, " + name;
}
var result2 = callSomeFunction(getGreeting, "Nicholas");
alert(result2); //"Hello, Nicholas"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 data = [{name: "Zachary", age: 28}, {name: "Nicholas", age: 29}];
data.sort(createComparisonFunction("name"));
alert(data[0].name); //Nicholas
data.sort(createComparisonFunction("age"));
alert(data[0].name); //Zachary


函数内部属性

函数内部有两个特殊对象:arguments和this。argements的主要用途是保存函数参数,此外这个对象还有一个callee属性。该属性是一个指针,指向拥有这个arguments对象的函数。
callee经典实现–阶乘函数:

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

定义阶乘函数一般用到递归算法。上面的代码所示,在函数有名字,且名字不会变的情况下,没有问题。但这个函数的执行与函数名factorial紧紧耦合了。为了消除这种耦合,使用arguments.callee。

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

函数内部特殊对象:this。this引用的是函数据以执行的环境对象-或者可以说是this值(当网页在全局作用域中调用函数时,this对象的引用就是window)。

1
2
3
4
5
6
7
8
window.color = 'red';
var o = { color: 'blue'};
function sayColor(){
alert(this.color);
}
sayColor();//red
o.sayColor = sayColor;
o.sayColor();//blue

上面函数sayColor()是在全局作用域中定义的,它引用了this对象。由于在调用函数之前,this的值并不确定,因此this可能会在代码执行过程中引用不同的对象。当在全局作用中调用sayColor()时,this引用的是全局对象window。当把函数赋给对象o并调用o.sayColor()时,this引用的对象是o,因此对this.color求值会转换成对o.color求值,结果返回blue。

ECMAScript5规范化了另一个函数属性 caller。这个属性保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值是null。

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
29
30
31
32
33
34
35
function outer(){
inner();
}
function inner(){
alert(inner.caller);
}
outer();
//同样为了实现松散的耦合,使用arguments.callee.caller
function inner(){
alert(arguments.callee.caller);
}

>当函数在严格模式下运行时,访问arguments.callee会导致错误。ECMAScript5还定义了arguments.caller属性。但在严格模式下,访问也会导致错误,在非严格模式下,这个属性始终是undefined。定义这个属性是为了区分arguments.caller和函数caller属性。
严格模式还有一个限制,不能为函数的caller属性赋值,否则会导致错误。


### 函数的属性和方法
ECMAScript中函数也是对象,因此函数也具有属性和方法。每个函数都包含两个属性: length和prototype。其中,length表示函数希望接收的命名参数个数。
对于ECMAScript中的引用类型而言,prototype是保存它们所有实例方法的真正所在。换言之,诸如toString()/valueOf()等方法实际上都保存在prototype名下。只不过是通过各自对象的实例访问。在创建自定义引用类型以及实现继承时,prototype属性的作用极为重要。**在ECMAScript中,prototype属性是不可枚举的,因此使用for-in无法发现。
>每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。

首先,apply接受两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中第二个参数可以是Array实例,也可以是arguments对象。

```javascript
function sum(num1, num2){
return num1 + num2;
}
function callSum1(num1, num2){
return sum.apply(this, arguments);//传入arguments对象
}
function callSum2(num1, num2){
return sum.apply(this, [num1, num2])
}
alert(callSum1(10,10));
alert(callSum2(10,10));

call()方法与apply()方法的作用相同,它们的区别在于仅在于接收参数的方式不同。对于call方法而言,第一个参数是this值没有变化,变化的
是其余参数都直接传递给函数。

1
2
3
4
5
6
function sum(num1, num2){
return num1 + num2;
}
function sum1(num1, num2){
return sum.apply(this, num1, num2);
}

apply()和call()最强大的地方在于能够扩充函数赖以运行的作用域。

1
2
3
4
5
6
7
8
9
window.color = 'red';
var o = {color: 'blue'};
function sayColor(){
alert(this.color);
}

sayColor.call(this);//red
sayColor.call(window);//window
sayColor.call(o);//blue

ECMAScript5还定义了一个方法: bind()。该方法会创建一个函数实例,其this值会绑定到传给bind函数的值。

1
2
3
4
5
6
7
window.color = 'red';
var o = {color: 'blue'};
function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor();//blue

sayColor()调用bind()并传入对象o,创建了objectSayColor()函数。objectSayColor()函数的this值等于o。因此即使在全局作用于中调用这个函数,也是输出blue。

每个函数继承toLocaleString()和toString()方法始终返回函数的代码。另外继承的valueOf()方法同样也只返回函数代码。