例证关于js中call和apply的误解

发现一些文章对call和apply的存在误解,因为误解的解释方式比较容易被理解吧,当然也有批判的,但都只是说说怎么才是对的,没有具体分析误解为什么是误解

今天让我们来仔细分析一下,为何不恰当

function add(a, b) {
     alert(a + b);
}
function sub(a, b) {
     alert(a - b);
}
add.call(sub, 3, 1);     //4


说词1:用 add 来替换 sub
说词2:call方法前面的东西(add)都交给后面(sub)了
说词3:sub 已经接过了add方法

恰当理解:
将add执行的上下文由window对象切换为对sub的引用,即this指向从window对象变为对sub的引用,仅此而已,这个例子其实没有什么实际上的意义

以上为从网络上摘选的观点
“说词”派说,看上去结果一样,凭什么这个理解就是恰当的?

OK,首先,我们来看下什么是上下文:

在JavaScript中,你的代码将总是有着某种形式的上下文(代码在其内部工作的对象)。上下文是通过变量this工作的,变量this总是引用代码当前所在的那个对象。全局对象实际上是window对象的属性,这意味着即使是在全局上下文里,this变量仍然引用一个对象。

搞清了上下文,那么我们来看这样一个函数

function test() {
     var myValue = 1;
     this.value = 2;
}

这个函数中,value很明显是存在于上下文中的
而myValue 是无法用this访问到的,类似私有变量的概念
问题来了,对于“说词”派的那些理解方式,“私有变量”也被传递了,所以结果是4,没有2

看到这里会不会有很奇怪的感觉了?真的是这样么?
我们再来分析,改写一下add和sub两个函数:

function add(a, b) {
     alert(inner(a, b));
}
function sub(a, b) {
     function inner() {
          this.value = arguments[0] - arguments[1];
          return this.value
     };
}
add.call(sub, 3, 1);

按照“说词”的解释,alert(inner(a, b)) 被传到 sub 中,那么结果为2
事实上,运行这段代码的时候,会发现抛错“inner is not defined”
“说词”派可能会站出来反驳了,add 都把 sub 整个覆盖了,肯定没有inner方法了

我们继续看,把add改一下

function add(a, b) {
     alert(this);
}
function sub(a, b) {
     function inner() {
          this.value = arguments[0] - arguments[1];
          return this.value
     };
}
add.call(sub, 3, 1); 

然后看输出


哦,买糕的,这是神马情况?怎么把sub函数代码体整个输出了?

“说词1”完全不对,sub被替换成add后怎么this还会是sub?
“说词2”和“说词2”也不对,如果是把add交个sub或sub接过了add的方法,那么在sub中执行alert(this)的结果是,this指向window
So,“说词”派,团灭

我们再来看一看,什么情况下会把函数代码体本身给输出来?

function test(a, b) { this.value = 1;}
console.log('test:', test );
console.log('test():', test() );
console.log('new test():', new test() ); 

输出结果:

输出函数的引用,才会把函数代码体本身给输出
我们再把“sub函数代码体整个输出”现象按“恰当解释”的说法走一边,其实是很容易解释的:
call和apply实现的是切换函数对象的上下文
把add的上下文this切换到sub的引用上,所以this把sub函数代码体整个给输出来了,事实上,这个做法完全没有什么意义,因为this.value根本就undefined

基于这一结论,我们把本文最开始的代码这样改造一下,此时sub才参与工作了

function add(a, b) {
     alert(a + b);
     this(a, b)     //此处相当于执行sub
}
function sub(a, b) {
     alert(a - b);
}
add.call(sub, 3, 1);     //4     //2
现在是不是更清晰更本质的认识到call的作用了?

不过,日常开发中,我们需要的是切换到一个有意义的上下文中,也就是this.value在切换上文后,能很方便的取到不同的值,我们发现new一个实例出来的结果为一个{}对象,最符合我们的需要,就像这样

function add(a, b) {
     this.inner(a, b);
}
function sub() {
     this.inner = function(a, b) {
          alert(a - b);
     };
}
add.call(new sub(), 3, 1);    //2


OK,扯了这么多,最后,再发一例更直接的干货,来看一段代码

function add(a, b) {
     this.inner(a, b);
}
function sub(a, b) {
     this.inner = function() {
          alert(arguments.callee.caller);
     };
}
add.call(new sub(), 3, 1);
此时arguments.callee相当于this.inner,当this.inner()执行的时候,arguments.callee才有效

再次根据“说词”派的解释,this.inner(a, b)交给了sub来执行,执行者是sub,那么this.inner()的caller必定指向sub
事实上并非如此,输出结果:


输出的结果是add
也就是说,call仅仅是将add的上下文替换成了sub,执行者仍然是add,不是sub


转载请注明出处:http://www.cnblogs.com/tevinli/p/4558107.html

posted on 2015-06-07 10:56  TevinLi  阅读(536)  评论(0编辑  收藏  举报

导航