JavaScript的arguments、caller和callee

标签:JavaScript

拜这该死的网站所赐,这篇文章写了2次才成功提交…
嘛,先给个参考文章,有兴趣的可以看看:《全面理解javascript的caller,callee,call,apply概念(修改版)》
我不会直接用他的例子,只是演示一下这几个东西而已。

首先是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); // false
	alert(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,我们可以更清楚地知道这是一个调用自身的递归调用。
甚至可以将这个函数对象传递给其他函数,即使该函数对象的函数名已经不再引用原函数了,仍可以用新函数名来实现对自身的调用。
而最后那段是个匿名函数的递归调用,看上去蛮有趣的~

0条评论 你不来一发么↓

    想说点什么呢?