今天在网上看到了一个有意思的js题目,就拿去和同事讨论。本来以为是一个很简单的问题,但越讨论越深入,逐步认识到了这个问题的深度。
题目是这样的:
function f1(){ alert(1); } function f2(){ alert(2); } var f3 = f1.call; f3.call(f2);?
?
?讨论的过程就不在赘述了,最后的结论是:
1 Function.prototype.call 实现的时候与是依赖与this的,如果直接调用f3(),浏览器将会报错。这个特性跟document.getElementById类似,比如在FF下,$=document.getElementById,调用$时浏览器会报错。
?
2 同样的对函数实例的call调用,如Function.prototype.call()与Function.prototype.call.call(Function.prototype与Function.prototype.call都是Function的实例),Function.prototype.call()可以正常调用,Function.prototype.call.call()则会抛出异常。
?
3 结合结论2以及题目本身,猜测是js引擎对call做了不同的实现,伪码如下:
?
var call = function(a,b,c){ this(b,c);//忽略this内部实现与a的绑定; } call.call = function(a,b,c){ a(b,c);//忽略this的绑定 } Function.prototype.call = call;
?这里猜测的是call本身作为Function的一个实例,在对call.call调用的时候,它的实例属性覆盖了它的原型属性。
?
4 但是通过比对 Function.prototype.call === Function.prototype.call.call 是为true的,所以结论3是错误的。应该是js内部为call的实现做了一个统一的托管,根据调用对象的不同,实现不同的逻辑。伪码如下:
?
function call(a,b,c){ if(this === call){ a(b,c); }else{ this(b,c); } } Function.prototype.call = call;
?
?
以上代码都最大化的做了简化,忽略了this的绑定以及参数的传递,只为了简单说明,请勿深究。有不妥的地方,欢迎拍砖。
?
其实这个结论4也不太准确,在我的另一篇博文http://rt0d.iteye.com/blog/1003754中,给出了证明。
function a(){ alert(1); } function global(){ return this; } a.call.call(); //找不到成员 a.call.call(global()); //找不到成员
call方法的第一个参数是它的调用者,如果不传入或传入null则默认是全局对象global(至少JScript是这样的,微软的手册里有写),call方法的真正含义是由用户自己来提供this参数,当call.call()的时候,没有提供call一个this,因此默认的global对象就成为了this参数,然而我们知道call函数是Funciton对象的成员,从一些现象上看很显然call是依赖了this也就是Function的数据结构,那当this变成global对象的时候,自然就异常了。
所以call函数本身不需要对自己的调用者做判断来探查是不是另一个call函数在调用自己,这样做很丑陋,更没有必要