在ECMAScript中函数实际上是对象。每一个函数都是Function类型的实例,而且都与其他援用类型1样具有属性和方法。由于函数是对象,因此函数名实际上也是1个指向函数对象的指针,不会与某个函数绑定。函数通常是使用函数声明语法定义的,如:
function sum(num1 , num2){
returnnum1 + num2;
}
这与下面使用函数表达式定义函数的方式几近相差无几:
var sum = function(num1 , num2){
returnnum1 + num2;
};
以上代码定义了变量sum并将其初始化为1个函数,上面的例子中function关键字后面没有函数名。这是由于在使用函数表达式定义函数时,没有必要使用函数名——通常变量sum便可以援用函数。另外,还要注意函数末尾有1个分号,就像声明其他变量1样。
最后1种定义函数的方式是使用Function构造函数。Function构造函数可以接收任意数量的参数,但最后1个参数始终都被看成是函数体,而前面的参数则枚举出了新函数的参数,如:
var sum = new Function(“num1” , “num2” , “returnnum1 + num2”);//不推荐
从技术角度讲,这是1个函数表达式。但是,不推荐使用这类方法定义函数,由于这类语法会致使解析两次代码(第1次是解析常规ECMAScript代码,第2次是解析传入构造函数中的字符串),从而影响性能。不过,这类语法对理解“函数是对象,函数名是指针”的概念倒是非常直观的。
由于函数名仅仅是履行函数的指针,因此函数名与包括对象指针的其他变量没有甚么不同。换句话说,1个函数可能会有多个名字。
将函数名想象为指针,也有助于理解为何ECMAScript中没有函数重载的概念,声明多个同名的函数,即便传入的参数的个数不1样,该名字也只属于最后1个函数。
解析器在向履行环境中加载数据时,会率先读取函数声明,并使其在履行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器履行到它所在的代码行,才会真正被解释履行,如:
alert(sum(10 , 20));//30
function sum(num1 , num2){
return num1 + num2;
}
//alert(add(10 , 20));//报错,停止向下解析履行
var add = function(sum1 , sum2){
return sum1 + sum2;
};
在上面的例子中分别使用函数声明和函数表达式定义了两个函数sum(num1 , num2)和add(sum1 , sum2),一样是函数,但是调用的结果却大相径庭。在sum前调用sum函数,能够正常履行,也就是在sum声明朝码行前调用该函数,当前环境中即存在该函数;但是对add函数,在声明朝码行对其进行调用却报错了。
接下来将调用代码调剂到add函数声明的后面:
var add = function(sum1 , sum2){
return sum1 + sum2;
};
alert(add(10 , 20));//30
此时的结果就正常了,从上面的对照中可以得出这样的结论,使用第1种方式声明的函数会在代码解析前被解析到当前环境中,而使用第2种方式声明的函数只有在解析器解析到对应的代码行时才会在当前环境中存在,此时看下面的例子就比较容易理解了:
function sum(num1 , num2){
return "第1个sum函数";
}
alert(sum(10, 20));//第3个sum函数
var sum = function(sum1 , sum2){
return "第2个sum函数";
};
function sum(num1 , num2){
return "第3个sum函数";
}
alert(sum(10 , 20));//第2个sum函数
上面的例子中声明了3个同名的函数,上面说到,JavaScript中没有重载的概念,函数名属于最后1个声明的函数实例。对上面代码中的第1个alert(),这个结论没有错,但是对最后的alert(),明显sum这个函数名指向的是第2个函数实例,造成这样的结果是由于第2个函数实例是最后1个被解析的,也就是说,环境中终究的sum变量指向了第2个函数实例。因此,此时就能够将上面的结论修改成:在JavaScript中没有重载的概念,多个重名的函数声明,该函数名属于最后1个被解析的函数实例。
也能够同时使用函数声明和函数表达式,如varsum = function sum(num1 , num2){},与单独使用函数表达式是等价的。不过,这类语法在Safari中会致使毛病。
由于ECMAScript中的函数名本身就是变量,所以函数也能够作为值来使用。也就是说,不但可以像传递参数1样把1个函数传递给另外一个函数,而且可以将1个函数作为另外一个函数的结果返回。如:
//将1个函数作为参数传递给另外一个函数
function add(num1 , num2){
return num1 + num2;
}
function raise(add , num1 , num2){
return add(num1 , num2)+1;
}
alert(raise(add, 10 , 10));//21
在Array类型中,其迭代方法的情势与上例中1致,如:
var numbers = [0,1,2,3];
var everyResult = numbers.every(function(item , index , array){
return (item > 2);
});
alert(everyResult);//false
var someResult = numbers.some(function(item , index , array){
return (item > 2);
});
alert(someResult);//true
将函数作为返回值返回是1种极其有用的技术,例如在数组排序时需要项sort()方法中传入1个比较函数,如果想在传入的函数中指定排序的方式,则可以以下进行操作:
function compare(asc){
alert(asc);
if(asc){
return function(value1 , value2){
if(value1 < value2){
return ⑴;
} else if(value1 > value2){
return 1;
}else{
return 0;
}
};
}else{
return function(value1 , value2){
if(value1 > value2){
return ⑴;
} else if(value1 < value2){
return 1;
}else{
return 0;
}
};
}
}
var array = [0,5,1,15,10];
array.sort(compare(false));
alert(array);
在compare函数中根据指定的asc属性判断返回降序排列的函数还是升序排列的函数。
在函数内部,有两个特殊的对象:arguments和this。其中arguments是1个类数组对象,包括着函数中的所有参数。虽然arguments的主要用处是保存函数参数,但这个对象还有1个名叫callee的属性,该属性是1个指针,指向具有这个arguments对象的函数,看下面的经典的阶乘函数:
function factorial(num){
if(num <=1){
return 1;
}else{
return num*factorial(num⑴);
}
}
alert(factorial(5));//120
定义阶乘函数1般都要用到递归算法;如上面的代码所示,在函数着名字,而且名字以后也不会变的情况下,这样定义没有问题。但问题是这个函数的履行与函数名factorial牢牢耦合在了1起。为了消除这类紧密耦合的现象,可以像下面这样使用arguments.callee:
function factorial(num){
if(num <=1){
return 1;
}else{
return num*arguments.callee(num⑴);
}
}
alert(factorial(5));//120
这个重写后的factorial()函数的函数体内,没有再援用函数名factorial。这样,不管援用函数时使用的是甚么名字,都可以保证正常完成递归调用,例如:
function factorial(num){
if(num <=1){
return 1;
}else{
return num*arguments.callee(num⑴);
}
}
var trueFactorial = factorial;
factorial = function(){
return 0;
};
alert(trueFactorial(5));//120
alert(factorial(5));//0
在此,变量trueFactorial取得了factorial的值,实际上是在另外一个位置上保存了1个函数的指针。然后,我们又将1个简单地返回0的函数赋值给factorial变量。如果像原来的factorial()那样不使用arguments.callee,调用trueFactorial()就会返回0。可是,在消除了函数体内的代码与函数名的耦合状态以后,trueFactorial()依然能够正常地计算阶乘;至于factorial(),它现在只是1个返回0的函数。
函数内部的另外一个特殊对象是this,其行动与Java和C#中的this大致类似。换句话说,this援用的是函数履行的环境对象——或也能够说是this值(当在网页的全局作用域中调用函数时this对象援用的就是winsow)。如:
console.log(this);//window
window.hello = "hello world";
alert(this.hello);//hello world
ECMAScript 5 也规范化了另外一个函数对象的属性:caller。除Opera的初期版本不支持,其他阅读器都支持这个ECMAScript3并没有定义的属性。这个属性中保存着调用当前函数的函数的援用,如果是在全局作用域中调用当前函数,它的值为null。如:
function outer(){
inner();
}
function inner(){
alert(inner.caller);
}
outer();
上面的的代码会致使正告框中显示outer()函数的源代码。由于outer()调用了inner(),所以inner.caller就指向outer()。为了实现更疏松的耦合,也能够通过arguments.callee.caller来访问一样的信息。
function outer(){
inner();
}
function inner(){
alert(arguments.callee.caller);
}
outer();
IE、Firefox、Chrome和Safari的所有版本和Opera9.6都迟滞caller属性。
当函数在严格模式下运行时,访问arguments.callee会致使毛病。ECMAScript5还定义了arguments.caller属性,但在严格模式下访问它也会致使毛病,而在非严格模式下这个属性始终是undefined。定义这个属性是为了分清arguments.caller和函数的caller属性。以上变化否是为了加强这门语言的安全性,这样第3方代码就不能在相同的环境里窥视其它代码了。
严格模式还有1个限制:不能为函数的caller属性赋值,否则会致使毛病。
ECMAScript中的函数是对象,因此函数也有属性和方法。每一个函数都包括两个属性length和prototype。其中length属性表示函数希望接收的命名参数的个数。
在ECMAScript核心所定义的全部属性中,最耐人寻味的就要数prototype属性了。对ECMAScript中的援用类型而言,proptotype是保存它们所有实例方法的真正所在。换句话说,诸如toString()和valueOf()等方法实际上都保存在prototype名下,只不过是通过各自对象的实例访问罢了。在创建自定义援用类型即实现继承时,prototype属性的作用是极其重要的。在ECMAScript5中,prototype属性是不可枚举的,因此使用for-in没法发现。
没个函数都包括两个非继承而来的方法:apply()和call(),这两个方法的用处都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。首先,apply()方法接收两个参数:1个是在其中运行函数的作用域,另外一个是参数数组。其中,第2个参数可以是Array的实例,也能够是arguments对象。如:
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));//20
alert(callSum2(10,10));//20
在上面这个例子中,callSum1()在履行sum()函数时传入了this作为this值(由于是在全局作用域中调用的,所以传入的就是window对象)和arguments对象。而callSum2一样也调用了sum()函数,但它传入的则是this和1个参数数组。这两个函数都会正常履行并返回正确的结果。
在严格模式下,为指定环境对象而调用函数,则this只不会转型为window。除非把函数添加到某个对象或调用apply()或call(),否则this值将是undefined。
call()方法与apply()方法的作用相同,它们的区分仅在于接收参数的方式不同。对call()方法而言,第1个参数是this值没有变化,变化的是其余参数都是直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐一罗列出来,如:
function sum(num1 , num2){
return num1 + num2;
}
function callSum1(num1 , num2){
return sum.call(this , num1 , num2);
}
alert(callSum1(10,10));//20
在使用call()方法的情况下,callSum()必须明确地传入每个参数。结果与使用apply()没有甚么不同。至因而使用apply()还是call(),完全取决于你采取哪一种给函数传递参数的方式最方便。
事实上,传递参数并不是apply()和call()真实的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域。如:
window.color = "red";
var o = {color:"blue"};
function sayColor(){
alert(this.color);
}
sayColor();//red
sayColor.call(this);//red
sayColor.call(window);//red
sayColor.call(o);//blue
这个例子中sayColor()是作为全局函数定义的,而且当在全局作用域中调用它时,它确切会显示”red”——由于this.color的值会转换成对window.color的求值。而sayColor.call(this)和sayColor.call(window),则是两种显式地在全局作用域中调用函数的方式,结果固然都会显示”red”。但是,当运行sayColor.call(o)时,函数的履行环境就不1样了,由于此时函数体内的this对象指向了o,因而结果显示的是”blue”。
使用call()或(apply())来扩充作用域的最大好处就是对象不需要与方法有任何耦合关系。在前面例子的第1个版本vzhong,先将sayColor()函数放到了对象o中,然后再通过o来调用它们的;而在这里重写的例子中,就不需要先前那个过剩的步骤了。
ECMAScript5还定义了1个方法bind()。这个方法会创建1个函数的实例,其this值会被绑定到传给bind()函数的值。如:
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”。
支持bind()方法的阅读器有IE9+、Firefox4+、Safari5.1+、Opera12+和Chrome。
每一个函数继承的toLocaleString()和toString()方法始终都返回函数的代码。返回代码的格式因阅读器而异。