JavaScript之Function类型
定义Function两种方式:
function sum (num1, num2) {
return num1 + num2;
}
var sum = function(num1, num2){
return num1 + num2;
};
以上代码定义了变量
sum
并将其初始化为一个函数。后一个例子中,function
关键字后面没有函数名。这是因为在使用函数表达式定义函数时,没有必要使用函数名——通过变量sum即可以引用函数。另外,还要注意函数末尾有一个分号,就像声明其他变量时一样。
最后一种定义函数的方式是使用
Function
构造函数。Function
构造函数可以接收任意数量的参数,但最后一个参数始终都被看成是函数体,而前面的参数则枚举出了新函数的参数。
var sum = new Function("num1", "num2", "return num1 + num2"); // 不推荐
从技术角度讲,这是一个函数表达式。但是,我们不推荐读者使用这种方法定义函数,因为这种语法会导致解析两次代码(第一次是解析常规ECMAScript代码,第二次是解析传入构造函数中的字符串),从而影响性能。不过,这种语法对于理解“函数是对象,函数名是指针”的概念倒是非常直观的。
没有重载(深入理解)
function addSomeNumber(num){
return num + 100;
}
function addSomeNumber(num) {
return num + 200;
}
var result = addSomeNumber(100); //300
这个例子中声明了两个同名函数,而结果则是后面的函数覆盖了前面的函数。以上代码实际上与下面的代码没有什么区别。
var addSomeNumber = function (num){
return num + 100;
};
addSomeNumber = function (num) {
return num + 200;
};
var result = addSomeNumber(100); //300
这个例子中在创建第二个函数时,实际上覆盖了引用第一个函数的变量
addSomeNumber
。
函数声明与函数表达式
函数声明与函数表达式的区别是,解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。
alert(sum(10,10));
function sum(num1, num2){
return num1 + num2;
}
正常
alert(sum(10,10));
var sum = function(num1, num2){
return num1 + num2;
};
以上代码之所以会在运行期间产生错误,原因在于函数位于一个初始化语句中,而不是一个函数声明。换句话说,在执行到函数所在的语句之前,变量
sum
中不会保存有对函数的引用;而且,由于第一行代码就会导致“unexpected identifier”(意外标识符)错误,实际上也不会执行到下一行。
注:
也可以同时使用函数声明和函数表达式,例如var sum = function sum(){}
。不过,这种语法在Safari中会导致错误。
作为值的函数
假设有一个对象数组,我们想要根据某个对象属性对数组进行排序。而传递给数组
sort()
方法的比较函数要接收两个参数,即要比较的值。可是,我们需要一种方式来指明按照哪个属性来排序。要解决这个问题,可以定义一个函数,它接收一个属性名,然后根据这个属性名来创建一个比较函数,下面就是这个函数的定义。
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
这里,我们创建了一个包含两个对象的数组
data
。其中,每个对象都包含一个name
属性和一个age
属性。在默认情况下,sort()
方法会调用每个对象的toString()
方法以确定它们的次序;但得到的结果往往并不符合人类的思维习惯。因此,我们调用createComparisonFunction("name")
方法创建了一个比较函数,以便按照每个对象的name
属性值进行排序。而结果排在前面的第一项是name
为"Nicholas"
,age
是29的对象。然后,我们又使用了createComparisonFunction("age")
返回的比较函数,这次是按照对象的age
属性排序。得到的结果是name
值为"Zachary"
,age
值是28的对象排在了第一位。
函数内部属性
在函数内部,有两个特殊的对象:
arguments
和this
。其中,arguments
是一个类数组对象,包含着传入函数中的所有参数。虽然arguments
的主要用途是保存函数参数,但这个对象还有一个名叫callee
的属性,该属性是一个指针,指向拥有这个arguments
对象的函数。请看下面这个非常经典的阶乘函数。
function factorial(num){
if (num <=1) {
return 1;
} else {
return num * factorial(num-1)
// return num * arguments.callee(num-1); //与上一行等价,但是消除了与函数名的紧密耦合的现象
}
}
函数内部的另一个特殊对象是
this
,其行为与Java和C#中的this大致类似。换句话说,this
引用的是函数执行的环境对象——或者也可以说是this
值(当在网页的全局作用域中调用函数时,this对象引用的就是window)。
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
sayColor(); //"red" 这次函数调用时,this指向的是函数sayColor执行的环境对象window
o.sayColor = sayColor; //给对象o动态添加属性,属性值为方法sayColor
o.sayColor(); //"blue" 这次调用时,sayColor执行的环境对象是o
注:
函数的名字仅仅是一个包含指针的变量而已。因此,即使是在不同的环境中执行,全局的sayColor()
函数与o.sayColor()
指向的仍然是同一个函数。
ECMAScript 5也规范化了另一个函数对象的属性:
caller
。除了Opera的早期版本不支持,其他浏览器都支持这个ECMAScript 3并没有定义的属性。这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为null
。
function outer(){
alert(outer.caller) // null
inner();
}
function inner(){
alert(inner.caller);
// alert(arguments.callee.caller) // 为了实现更松散的耦合,也可以通过arguments.callee.caller来访问相同的信息
}
outer();
注:
当函数在严格模式下运行时,访问arguments.callee
会导致错误。ECMAScript 5还定义了arguments.caller
属性,但在严格模式下访问它也会导致错误,而在非严格模式下这个属性始终是undefined
。定义这个属性是为了分清arguments.caller
和函数的caller
属性。以上变化都是为了加强这门语言的安全性,这样第三方代码就不能在相同的环境里窥视其他代码了。
严格模式还有一个限制:不能为函数的caller
属性赋值,否则会导致错误。
严格模式中,递归函数使用arguments.caller
(或者callee
)会引起this
混乱出现
var global = this;
var sillyFunction = function(recursed) {
if (!recursed) { return arguments.callee(true); }
if (this !== global) {
alert('This is: ' + this); // 若!recursed为false,则this!==global
} else {
alert('This is the global'); // 若!recursed为true,则this===global
}
}
sillyFunction();
函数属性和方法
ECMAScript中的函数是对象,因此函数也有属性和方法。每个函数都包含两个属性:
length
和prototype
。
length
属性表示函数希望接收的命名参数的个数
function sayName(name){
alert(name);
}
function sum(num1, num2){
return num1 + num2;
}
function sayHi(){
alert("hi");
}
alert(sayName.length); //1
alert(sum.length); //2
alert(sayHi.length); //0