Function类型
函数是对象,每个函数都是Function类型的实例,而且与其它引用类型一样具有属性和方法。。
由于函数是对象,因此函数名实际上是一个指向函数对象的指针 ,不会与某个函数绑定。
function sum(num1, num2){
return num1 + num2;
}
// 函数表达式定义函数
var sum = function(num1, num2){
return num1 + num2;
}
由于函数名仅仅是指向函数的指针,因此函数名与包含对象指针的其它变量没有什么不同。
function sum(num1, num2){
return num1 + num2;
}
alert(sum(10,10)); // 20
var anotherSum = sum;
alert(anotherSum(10,10)); // 20
sum = null;
alert(anotherSum(10,10)); // 20
声明变量anotherSum,并将其设置为与sum相等(将sum的值赋给anotherSum)。
注意:使用不带圆括号的函数名是访问函数指针,而非调用函数。
此时,anotherSum和sum都指向了同一个函数。即使将sum设置为null,仍然可以正常调用anotherSum()。
作为值的函数
因为ECMAScript中的函数名本身就是变量,所以函数也可以作为值来使用。也就是说,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回 。
函数作为参数传递
function callSomeFunction(someFunction, someArgument){
return someFunction(someArgument);
}
function add10(num){
return num + 10;
}
var result = callSomeFunction(add10, 10);
alert(result); // 20
function getGreeting(name){
return "Hello, " + name;
}
result = callSomeFunction(getGreeting, "Nicholas");
alert(result); // "Hello, Nicholas"
函数作为函数的返回值
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);
函数的内部属性
在函数内部,有两个特殊的对象:arguments和this。
arguments属性
arguments是一个类数组对象,包含着传入函数中的所有参数。
arguments有一个callee指针属性,指向拥有这个arguments对象的函数。
阶乘函数:
function factorial(num){
if(num <= 1){
return 1;
}else{
return num * factorial(num -1);
}
}
函数名字不变的情况下,这样定义没问题。但问题是这个函数的执行与函数名factorial紧紧耦合在了一起。为了消除这种紧密耦合的现象,可以使用arguments.callee。
function factorial(num){
if(num < 1){
return 1;
}else{
return num * arguments.callee(num -1);
}
}
这样,无论引用函数时使用的什么名字,都可以保证正常完成递归调用。
var trueFactorial = factorial;
factorial = function(){
return 0;
}
alert(trueFactorial(5)); // 120
alert(factorial(5)); // 0
变量trueFactorial获得了factorial的值,实际上是在另一个位置上保存了一个函数的指针。然后,我们又将一个简单地返回0的函数赋值给factorial变量。如果不使用arguments.callee,调用trueFactorial()就会返回0。
this属性
* this引用的是函数执行时的环境对象 (当在网页的全局作用域中调用函数时,this对象引用的就是window)* 。
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;换句话说,对this.color求值会转换成对window.color求值,于是结果返回了”red”。当把这个函数赋给对象o,并调用o.sayColor()时,this引用的是对象o,因此对this.clor求值会转换成对o.color求值,结果返回了”blue”。
注意:函数的名字仅仅是一个包含指针的变量而已。因此,即使是在不同的环境中执行,全局的sayColor()与o.sayColor()指向的仍然是同一个函数。
函数的属性
每个函数都包含两个属性:length和prototype。
length属性
表示函数希望接收的命名参数的个数
function sayHi(){
alert("hi");
}
function sayName(name){
alert(name);
}
function sum(num1, num2){
return num1 + num2;
}
alert(sayHi.length); // 0
alert(sayName.length); // 1
alert(sum.length); // 2
prototype属性
对于ECMAScript中的引用类型而言,prototype是保存它们所有实例方法的真正所在。换句话说,诸如toString()和valueOf()
等方法实际上都保存在prototype名下,只不过是通过各自对象的实例访问罢了。在创建自定义引用类型以及继承时,prototype属性的作用是极为重要的。
函数的方法
每个函数都包含两个非继承而来的方法:apply()和call()。这两个方法的用途都是* 在特定的作用域中调用函数,实际上等于设置函数体内this对象的值 * 。
apply方法
接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中。第二个参数可以是Array的实例,也可以是arguments对象。
function sum(num1, num2){
return num1 + num2;
}
function callSum1(num1, num2){
sum.apply(this, arguments); // 传入arguments对象
}
function callSum2(num1, num2){
sum.apply(this, [num1, num2]); // 传入数组
}
alert(callSum1(10, 10)); // 20
alert(callSum2(10, 10)); // 20
callSum1()在执行sum()时传入了this作为this值(因为是在全局作用域中调用的,所以传入的就是window对象)。
call方法
call()和apply()作用相同,区别仅在与接收参数的方式不同。call()的第二个参数必须逐个列举出来。
function sum(num1, num2){
return num1 + num2;
}
function callSum(num1, num2){
sum.call(this, num1, num2);
}
alert(callSum(10, 10)); // 20
扩充函数赖以运行的作用域
apply()和call()的真正强大的地方是能够扩充函数赖以运行的作用域。
window.color = "red";
var o = {color: "blue"};
function sayColor(){
alert(this.color);
}
sayColor.call(this); // red
sayColor.call(window); // red
sayColor.call(o); // blue
使用call()(或apply())来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。在前面的代码中,我们先将sayColor()放到了对象o中,然后再通过o来调用它的;而使用call(),就不需要那个多余的步骤了。