JavaScript的arguments、caller和callee

来源:互联网 发布:淘宝联盟的预估收入 编辑:程序博客网 时间:2024/04/30 03:21

转载keakon 原文 http://www.keakon.net/2009/04/30/JavaScript%E7%9A%84arguments%E3%80%81caller%E5%92%8Ccallee

写的不错,收藏了。

首先是arguments。

它是在函数调用时,自动在该函数内部生成的一个名为arguments的隐藏对象。该对象类似数组,可以用[]操作符获取函数调用的时传递的实参:
function write(str) {document.write((str == undefined ? '' : str) + '<br />');}function testArg() {write('实参个数:' + arguments.length);for (var i = 0; i < arguments.length; ++i) {write(arguments[i]);}write();}testArg(123);testArg('hi');testArg('keakon', 'loli');
结果:
实参个数:1
123

实参个数:1
hi

实参个数:2
keakon
loli
很显然,我们可以用这个对象来实现可变参数与缺省参数。

此外还能指定arguments所属的函数:
function write(str) {document.write((str == undefined ? '' : str) + '<br />');}function aCallee() {write('aCallee的实参个数:' + arguments.length);write('aCaller的实参个数:' + aCaller.arguments.length);}function aCaller() {aCallee(789);}aCaller(123, 456);
结果:
aCallee的实参个数:1
aCaller的实参个数:2
也就是说,如果arguments前未跟函数名,则取当前函数的arguments,否则取指定的函数。

此外,虽然arguments的行为像数组,但从技术上而言,它并不是一个数组对象:
(function () {alert(arguments instanceof Array); // falsealert(typeof(arguments)); // object})();
另外,只有函数被调用时,arguments对象才会创建,未调用时其值为null:
alert((function() {}).arguments);//或者alert(new Function().arguments);


接着来看caller。
在一个函数调用另一个函数时,被调用函数会自动生成一个caller属性,指向调用它的函数对象。如果该函数当前未被调用,或并非被其他函数调用,则caller为null。
function write(anObject) {document.write('<pre>' + (anObject == null ? 'null' : anObject.toString()) + '</pre>');}function testCaller() {var caller = testCaller.caller;write(caller);}function aCaller() {testCaller();}testCaller();aCaller();
结果:
null
function aCaller() {
    testCaller();
}
实际上function对象是可以直接write的,我调用toString()方法是为了表示获取的不是字符串,而是函数本身。不过由于JavaScript的特殊性,我们可以直接输出函数代码,不需要什么反汇编的过程。


最后来看callee。
当函数被调用时,它的arguments.callee对象就会指向自身,也就是一个对自己的引用。
由于arguments在函数被调用时才有效,因此arguments.callee在函数未调用时是不存在的(即null.callee),且解引用它会产生异常。
此外,arguments.callee.length可以获取形参的个数。不过与arguments不同的是,arguments.callee无法获得形参的值(你应该获取实参的值)。
而且由于arguments可以指定函数名,所以arguments.callee自然也可以:
function write(str) {document.write((str == undefined ? '' : str) + '<br />');}function aCallee(arg) {write('aCallee的形参个数:' + arguments.callee.length);write('aCaller的形参个数:' + aCaller.arguments.callee.length); // 等效于arguments.callee.caller.arguments.callee.length或arguments.callee.caller.length}function aCaller(arg1, arg2) {aCallee();}aCaller();
结果:
aCallee的形参个数:1
aCaller的形参个数:2
或许有人会问callee有什么用,这就不得不提this对象了。

由于JavaScript的函数实际上是个对象,在对象内部使用this理论上是会指向对象自身的。但JavaScript的this并非如此,在不同的上下文中,它指向的对象是不同的。
这里有篇《[图解] 你不知道的 JavaScript - “this”》很好地阐述了this对象。

简单来说,函数有个作用域(即它在哪个对象里创建的),一般来说,该作用域或对象即为this对象。(可以用apply和call方法更改作用域;此外,用new操作符创建对象时,this是指向这个对象自身的。)
当我们在全局作用域(window对象里)定义函数,并调用它时,它的this对象即为window对象。
如果为对象obj定义了一个f方法,然后用obj.f()来调用,那么f方法里的this对象即为obj。
也就是说,想在f方法里拿到f方法自身的引用,你需要用obj.f或this.f。但这并非一个通用的办法,假若是g方法的话,你就得改成obj.g或this.g。而如果是匿名函数的话,甚至无法用这种方式获取。
所以callee的出现就解除了方法与方法名的耦合,使得获取方法自身变得更为通用。同时,它也为递归调用提供了更好的可读性,并让引用匿名函数对象成为了可能:
function fibonacci(num) {if (num < 3) {return 1;} else {return arguments.callee(num - 1) + arguments.callee(num - 2); // 如果用fibonacci(num - 1) + fibonacci(num - 2),在更换函数名后,对fibonacci的调用就会出错}}var f = fibonacci;fibonacci = null;alert(f(10));alert((function (num){if (num < 3) {return 1;} else {return arguments.callee(num - 1) + arguments.callee(num - 2);}})(10));
可以看到,由于使用了arguments.callee,我们可以更清楚地知道这是一个调用自身的递归调用。
甚至可以将这个函数对象传递给其他函数,即使该函数对象的函数名已经不再引用原函数了,仍可以用新函数名来实现对自身的调用。
原创粉丝点击