在函数内部,有两个特殊的对象:arguments和this。其中,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); } }
在这个重写后的factorial()函数的函数体内,没有再引用函数名factorial。这样,无论引用函数时使用的是什么名字,都可以保证正常完成递归调用。例如:
var trueFactorial=factorial; factorial=function() { return 0; }; alert(trueFactorial(5));//120 alert(factorial(5));//0
在此,变量trueFactorial获得了factorial的值,实际上是另一个位置上保存了一个函数的指针。然后,我们又将一个简单的返回了0的函数赋值给了factorial变量。如果像原来factorial()那样不用使用arguments.callee,调用trueFactorial()就会返回0。可是,在解除了函数体内的代码与函数名的耦合状态之后,trueFactorial()仍然能够正常的计算阶乘;至于factorial(),它现在只是一个返回0的函数。
函数内部的另一个特殊对象是this,其行为与java和c#中的this大致类似。换句话说,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.color求值会转换成对o.color求值,结果就返回了“blue”。
注:请牢记,函数的名字仅仅是一个包含指针的变量而已。因此,即使是在不同的环境中执行,全局的sayColor()函数与o.sayColor()指向的仍然是同一个函数。
ECMAScript 5 也规范了另一个函数对象的属性:caller。除了Opera的早期版本不支持,其他浏览器都支持这个ECMAScript 3 并没有定义的属性。这个属性中保存着调用当前函数的函数的引用,如果是在前全局作用域中调用当前函数,它的值是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、FF、Chrome和Safari的所有版本以及Opera9.6都支持caller属性。
当函数在严格模式下运行时,访问arguments.callee会导致错误。ECMAScript 5还定义了arguments.caller属性,但在严格模式下访问也会导致错误,而在非严格模式下这个属性始终是undefined。定义这个属性是为了分清arguments.caller和函数的caller属性。以上变化都是为了嘉庆这门语言的安全性,这样第三方代码就不能在相同的环境里窥视其他代码了。
严格模式还有一种限制:不能为函数caller函数赋值,否则会导致错误。