引言
在JavaScript中最有意思的就是函数了,这一切的根源在于函数实际上是一个对象。每一个函数都是Function类型的实例,而且都和其他引用类型的实例一样具有属性和方法。函数作为一个对象,因此函数名称实际是一个指向函数对象的指针,不会与某一个函数进行绑定。
函数没有重载
前面部分介绍过,函数名称实际是指向函数对象的一个指针,这样就不难理解Javacript中的函数不存在函数重载了。请看下面的例子
function addNumber(number) {
return 100 + number;
} function addNumber(number) {
return 200 + number;
} var result = addNumber(300);
alert(result);//输出500
在这个例子里面我们定义了两个同名函数,第一个函数我们在定义的时候会在全局作用域的变量对象中引用第一个函数对象,addNumber指向第一个函数对象。当运行到第二个函数的时候addNumber又指向第二个函数对象。所以addNumber现在指向第二个函数对象。所以下面在调用它引用的函数时,会执行第二个函数。
函数声明与函数表达式
函数通常是使用函数声明语法定义的,当然函数表达式的语法也是可以的,不过它们之间有一些很重要的差异。实际上,解析器在向执行环境中加载数据时,会率先读取函数表达式,使其在任何代码之前可用(可以访问)。对于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正解析执行。
作为值的函数
在JavaScript中函数名本身就是变量,因此函数也可以作为值来使用。函数不仅可用作为参数传递给另一个参数,也可以作为另一个函数的结果返回。我们来看下下面的例子
//通过属性名称来对数组元素进行排序
function createComparisonFunction(propertyName) {
return function (obj1, obj2) {
var val1 = obj1[propertyName];
var val2 = obj2[propertyName];
if (val1 < val2) {
return -1;
}
else if (val1 > val2) {
return 1;
}
else {
return 0;
}
}
} var data = [{ name: "gxw", age: 25 }, { name: "zmm", age: 20 }];
//按照name属性来排序
data.sort(createComparisonFunction("name"));
alert(data[0].name);//输出gxw
//按照age属性来排序
data.sort(createComparisonFunction("age"));
alert(data[0].name);//输出zmm
在这个例子中我们看到我们在createcomparisonFunction中返回了一个匿名函数,在匿名函数中我们根据属性来对对象数组进行排序。
函数内部属性
在函数内部有两个非常特殊的对象,它们分别是:arguments和this。arguments对象在前面可能也介绍了一点。它是一个类似于数组的对象,但是它不是数组。它包含着传入函数的所有的参数。arguments对象还有一个callee属性,这个属性是一个指针,指向拥有这个arguments对象的函数。一个非常经典的例子是阶乘,请看例子:
function factorial(number) {
if (number <= 1) {
return 1;
}
else {
//return number * factorial(number - 1);
return number * arguments.callee(number - 1);
}
} alert(factorial(5));
在这个例子中我们看到我们定义了一个名叫factorial的阶乘函数。在第6行一般是我们常规的做法。但是这样有一个问题,阶乘函数和它的名称紧紧的耦合在一起,一旦以后改变了阶乘函数的名称,就需要在函数内部也修改名称。这时候通过使用arguments.callee可以代替第一种做法。消除了函数与名称紧紧耦合在一起的问题。
在函数内部this对象引用的是函数据以执行的环境对象(当在网页的全局作用域中调用函数时,this对象引用的就是window对象)。来看下面的例子:
var color = "windows color";
var obj = { color: "obj color" }; function sayColor() {
alert(this.color);
} function sayColor2() {
return function () {
return this.color;
}
} sayColor(); //windows color obj.sayColor = sayColor;
obj.sayColor();//obj color alert(sayColor2()()); //windows color obj.sayColor2 = sayColor2;
alert(obj.sayColor2()());//windows color
在这个例子中,我们看到在全局作用域下调用函数this指向的是window对象。看代码的第17行。如果在对象上调用函数的时候,我们看到this指向的当前调用函数的对象。重点是如果在函数内部还有一个函数的时候this指向的是什么呢?通过sayColor2函数我们知道在匿名函数中this指向的是window对象。那为什么不是包含作用域(或者外部作用域)的this对象呢?在前面我们介绍过,在函数被调用时,其活动对象都会自动获得2个特殊对象,this和arguments对象。内部函数在这两个变量时,只会搜索到活动对象为止,所以用于不可能获取到外部作用域中的这2个对象。
函数属性和方法
JavaScript中函数时对象,因此函数也有属性和方法。每一个函数都包含两个属性,它们分别是:length和prototype属性。length属性表示函数希望接受到的参数的个数。例如一个函数在定义的时候参数列表定义了3个参数,那么length就是3。记住:定义的参数个数可以通过函数名.length来获取。实际传递的参数个数和参数值可以到arguments中获取。
在JavaScript定义的全部属性中,最神秘的就是prototype属性了。对于JavaScript中的引用类型而言,prototype是保存它们所有实例方法的真正所在。在JavaScript中prototype是不可枚举的,所以for-in循环是找不到prototype属性的。
每个函数都包含两个非继承而来的方法,call和apply方法。这两个函数的用途都是在特定的作用域中调用函数,实际上是等于设置函数体内this对象的值。事实上,传递参数并非是call和apply真正 用武之地,它们强大的地方在于可以扩充函数赖以生存的作用域。来看下面的例子:
var color = "windows color";
var obj = { color: "obj color" }; function sayColor() {
alert(this.color);
} sayColor(); //windows color sayColor.call(this); //windows color
sayColor.call(window); //windows color
sayColor.call(obj); //obj color
注意看代码的第12行,通过call函数指定函数体内this的值是obj对象。这样输出的就是obj color了。使用call或者apply来扩充作用域就是对象不需要与函数有任何耦合关系。在前面的例子中,我们先将sayColor绑定到obj对象上,然后使用obj.sayColor()来调用函数。通过call或者apply我们可以省去那些不必要的步骤。