在JavaScript中,函数实际是对象。每个函数都是Function类型的实例,而且都与其他类型一样具有属性和方法。函数声明方式:
// 第一种:使用函数声明语法定义 function sum (num1, num2) { return num1 + num2; } // 第二种:使用函数表达式定义函数 var sum = function(num1, num2) { return num1 + num2; }; // 第三种:构造函数 var sum = new Function("num1", "num2", "return num1+num2");
由于函数是对象,因此函数名实际上是一个指向函数的指针,不会与某个函数绑定。函数名与包含对象指针的其他变量没有什么不同,也就是说,一个函数可能有多个名字:
function sum(num1, num2) { return num1 + num2; } alert(sum(1,2)); // 3 var anotherSum = sum; alert(anotherSum(3,4)); // 7 sum = null; alert(anotherSum(4, 5)); // 9
需要注意的是sum = null
将sum变量与函数取消了关系,但是仍然可以正常调用anotherSum()。
没有重载
JavaScript中并没有类似于java语言的重载机制。
function addNum(num) { return num + 1; } function addNum(num) { return num + 2; } var result = add(0); // 2
上面的例子声明了两个函数,而结果显示后面的函数覆盖了前面的函数。上面的代码等价于:
function addNum(num) { return num + 1; } function addNum(num) { return num + 2; } var result = add(0); // 2
函数声明与函数表达式
JavaScript解析器在向执行环境中加载数据时,会率先读取函数声明,并使其在执行任何代码之前可用;至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。
alert(sum(1, 2)); function sum(num1, num2) { return num1 + num2; }
上面的代码可以正常执行。这是因为在代码执行之前,解析器就已经通过名为函数声明提升的过程,读取并将函数声明添加到执行环境中。对代码求值时,JavaScript引擎也能把函数声明提升到顶部。
如果把函数声明改为函数表达式的形式,就会在执行期间导致错误。
alert(sum(1, 2)); var sum = function(num1, num2) { return num1 + num2; };
以上代码会运行错误。因为函数位于初始化语句中,在执行到函数所在的语句之前,变量sum中不保存对函数的引用。
作为值的函数
在JavaScript中的函数本身就是变量,所以函数也可以作为值来使用。这就是说,可以把函数当作一个参数传递给另一个变量。
function sum(add1, arg) { return add1(arg); }
sum函数接收两个参数。第一个是参数是一个函数,第二个参数应该是要传递给该函数的一个值。
// add1执行加1操作 function add1(num) { return num + 1; } var result = sum(add1, 2); alert(result); // 3 function sayHello(name) { return "hello, " + name; } var result1 = sum(sayHello, "world"); alert(result1); // "hello, world"
注意:这里sum()函数是通用的,无论第一个参数传进来的是什么函数,都返回这个函数的结果。sum(add1, 2);和sum(sayHello, "world");中传递的都是add1()方法和sayHell0()方法,而不是执行它们返回的结果。
可以从一个函数中返回另一个函数。例如前面提到的数组的sort()方法的比较函数,需要传入两个参数。
function createComparisionFunction(propertyName) { return function(obj1, obj2) { var value1 = obj1[propertyName]; var value2 = obj2[propertyName]; if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } }; }
上面定义了一个函数,这个函数中嵌套了一个函数,内部函数接收到propertyName参数后,会使用方括号表示法来取得给定属性的值,然后根据值来定义排序规则。
var data = [{name:"zhangsan", age:18}, {name:"lisi", age:19}]; data.sort(createComparisionFunction("name")); alert(data[0].name); // lisi data.sort(createComparisionFunction("age")); alert(data[0].name); // zhangsan
函数内部属性
在函数内部,有两个特殊对象:arguments和this。
arguments
arguments是一个类数组对象,包含传入函数中的所有参数。arguments主要用于保存函数参数,除此之外还有一个callee属性,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 * factorial(num - 1); } }
这样,无论引用函数时是什么名字都不影响函数的正常运行。例如:
var newfactorial = factorial; factorial = function () { return 0; } alert(newfactorial(5)); // 120 alert(factorial(5)); // 0
this
this引用的是函数据以执行的环境对象。
window.color = "red"; var obj = {color: "blue"}; function sayColor() { alert(this.color); } sayColor(); // "red",this指window对象 obj.sayColor = sayColor; obj.sayColor(); // "blue", this指obj对象
在调用函数之前this的值并不确定,this在运行过程中会指代不同对象。在调用sayColor();时,this引用的是全局对象window,this.color相当于window.color;当把sayColor赋值给obj对象并调用obj.sayColor()时,this引用的是obj对象,因此对this.color即obj.color。
caller
ECMAScript5提供了另一个函数对象的属性:caller。它保存着调用当前函数的函数的引用,如果在全局作用域中调用当前函数,值为null。
function outer() { inner(); } function inner() { alert(inner.caller); } outer();
上面的代码会显示outer()函数的源码。因为outer()调用了inner(),所以inner.caller就执行outer()。
函数属性和方法
前面已经说过,在JavaScript中函数时对象,因此函数也有属性和方法。每个函数。每个函数都包含两个属性:length和prototype。
length属性保存的是调用数组时的参数个数,而不是声明时定义的参数个数。对于JavaScript中的引用类型而言,prototype是保存它们所有实例方法的真正所在。诸如toString()和valueOf()等方法实际上都保存在prototype名下,只不过是通过各自对象的实例访问。在ECMAScript5中,prototype属性是不可枚举的,因此,使用for-in无法发现。
每个函数都包含两个非继承而来的方法:apply()
和call()
。这两个方法都是在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。
apply()方法
apply()方法接收两个参数:第一个参数是在其中运行函数的作用域;另一个是参数数组,它可以是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(1, 2)); // 3 alert(callSum2(1, 2)); // 3
call()方法
call()方法与apply()方法的作用相同,它们的区别仅在于接收的参数不同。第一个参数也是this,其余参数都直接传递。
function sum(num1, num2) { return num1 + num2; } function callSum(num1, num2) { return sum.call(this, num1, num2); } alert(callSum(1, 2)); // 3
在使用call()方法的情况下,callSum()必须明确传入每一个参数,返回值都一样。
apply()和call()最大的作用在于能够扩充函数赖以运行的作用域。
window.color = "red"; var obj = {color: "blue"}; function sayColor() { alert(this.color); } sayColor(); // red sayColor.call(this); // red sayColor.call(window); // red sayColor.call(obj); // blue
在上面的例子中,sayColor()方法是定义在全局作用域中的,在全局作用域中this.color
会转换为window.color
,所以sayColor()
的结果为red。而sayColor.call(this)
和sayColor.call(window)
显式地在全局作用域中调用函数,结果必然为热点。当运行sayColor.call(obj)
时,函数的运行环境就变成了obj,当执行sayColor()
时,this.color
会转换为obj.color
,因此结果为blue。
bind()方法
bind()方法会创建一个函数的实例,其this值会被绑定到传给bind()函数的值。
window.color = "red"; var obj = {color: "blue"}; function sayColor() { alert(this.color); } var objSayColor = sayColor.bind(obj); objSayColor(); // blue
在上面的例子中,sayColor()调用bind()并传入对象。创建了objSayColor()函数。objSayColor()函数的this值为obj,因此,即使在全局作用域下调用objSayColor(),结果也是blue。