走心大白话 JavaScript 教程(二)巧妙理解 call 和 apply

JS大法好,JS在手,天下我有,信JS,得永生。

这个系列的教程我一开始是写在github上的,
可是以为放到掘金来可让更多须要的人看到,
就搬到掘金专栏上啦,
若是以为本教程对你有帮助,请点这里去github上给我一颗Star~
教程目录也在github上哈~javascript

本着对技术负责的态度,任何纠正/疑问,尽管提出,我会及时修正/回答。

必定要把每一个例子代码都拷贝到你的运行环境中边看结果边理解,否则学习效果减半,或者没效果。

下面开始第三篇:java

巧妙理解 call 和 apply

想当年我仍是个小白的时候,看到call和apply,那都是一脸懵逼啊!
再加上参数内部this,arguments什么的,虐的我不要不要的,一度产生厌学心理。
的确,这俩方法对初学者不够友好...git

可是!做为半个老鸟,如今看到call啊什么apply啊什么的,也就微微一笑了。
想当初茅塞顿开的时候,那内心叫一个痛快,如今就把开窍的过程分享出来。github

一、call和apply的区别

先说一下call和apply的区别,你在彻底不懂俩函数是干吗的状况下,你只要记住:
call和apply的功能是彻底同样的,只是第二个参数不同;
call能够接收无限多个参数,apply只接收俩参数,而且第二个参数只能是argument。
“而它们一样的第一个参数,就是新的this指向!”
你先不用管引号里的话说明了什么,脑子里默记下这句话就行。
好了,如今,不要多想,往下看。app

二、call、apply会改变this指向

我在实际应用中,最经常使用的就是用call、apply去“借”另外一个对象的方法来用,实际上是call、apply改变了this指向。复制代码

上最简单的栗子

我写了个对象obj1,内部三个属性,两个数字numA、numB、还有个方法add,能够打印numA和numB之和:复制代码
var obj1 ={
    numA:1,
    numB:2,
    add:function(){
         console.log(this.numA + this.numB)
    }
}
obj1.add(); //打印出obj1.numA和obj1.numB的和,即3复制代码
如今我写了个对象obj2,内部有只两个属性数字numA和数字numB,没有计算器,但也想求和,怎么办?
管obj1借啊!怎么借?call、apply啊!
上代码复制代码
var obj2 = {
    numA:3,
    numB:4
}
//用call借:
obj1.add.call(obj2); //打印出obj2.numA和obj2.numB的和,即7;
//用apply借:
obj1.add.apply(obj2); //打印出obj2.numA和obj2.numB的和,即7;复制代码
有意思吧?明明是obj1的add方法里出现了this,按照《理解JS中this指向的小技巧》中的思路,
找到的“.”左边是obj1,说明是obj1调用了add,add方法内部的this应该指向obj1啊!为啥算出来的结果都是obj2里的numA与numB之和呢?
由于用了call和apply啊!不是刚说完嘛,它们改变了this的指向啊,指向谁啊?第一个参数啊!第一个参数是谁啊?obj2啊!
因此你写obj1.add.call(obj2),add方法内部的this指向就变成了obj2,就打印出了obj2.numA和obj2.numB的和。
就起到了obj2向Obj1“借”了方法add的效果。复制代码

带参数的栗子

这个栗子是面向对象的栗子,对面向对象不够了解的同窗,请尽可能读懂不得不提的原型/原型链
函数

我写了个构造函数Obj1,内部三个属性,两个数字numA、numB、还有个方法add,能够打印numA和numB之和:复制代码
function Obj1(numA,numB){
    this.numA = numA;
    this.numB = numB;
}
Obj1.prototype.add = function(){
         console.log(this.numA + this.numB)
}
var obj1 = new Obj1(1,2);
obj1.add(); //打印出obj1.numA和obj1.numB的和,即3复制代码
如今我写了个构造函数Obj2,内部有只两个属性数字numA和数字numB,没有计算器,但也想求和,怎么办?
管obj1借啊!怎么借?call、apply啊!
上代码复制代码
function Obj2(numA,numB){
    this.numA = numA;
    this.numB = numB;
}
var obj2 = new Obj2(3,4);
//用call向实例obj1借:
obj1.add.call(obj2,3,4); //打印出obj2.numA和obj2.numB的和,即7;
//用apply向实例obj1借:
obj1.add.apply(obj2,[3,4]); //打印出obj2.numA和obj2.numB的和,即7;
//用call向构造函数Obj1借:
Obj1.prototype.add.call(obj2, 3, 4); //打印出obj2.numA和obj2.numB的和,即7;
//用apply向构造函数Obj1借:
Obj1.prototype.add.apply(obj2, [3, 4]); //打印出obj2.numA和obj2.numB的和,即7;复制代码
这个栗子刚好说明了带参数的状况怎么“借”另外一个对象的方法,也把apply和call的不一样解释明白了,就是个传参不一样。
看这个 Obj1.prototype.add.call(obj2, 3, 4) ,眼熟吗?
像不像 Array.prototype.forEach.call(xxx) ?就是这么来的,xxx想借用Array.prototype的forEach方法完成遍历。复制代码

三、特殊栗:在第一个参数为this而且this指向window的状况下,apply的应用

好比有个需求,须要作到每次调用先前别人写好的方法时,先在前面运行咱们添加的代码:
下面的代码不必定是最好的实现本需求的代码,但能够演示apply的应用。
生动的具体化一下:学习

先前陈海写的的代码:
function foo(){
        console.log('我是陈海,我拍床戏去了');
    }
    foo();复制代码
如今侯亮平接手的反贪局接管了代码,
需求是,不改变陈海写的代码的状况下,在每次调用陈海写的代码时先打印一些话。复制代码
林华华挺身而出,用一段代码帮侯局长完成了需求:
function beforeFoo(num){
        console.log('侯亮平知道陈海有床戏,一共'+num+'场');
    }
    var fooOld = foo;
    foo = function(num){
        beforeFoo(num); //这里将会被陆亦可修改
        fooOld();
    }
    foo(30); //运行一下看看效果复制代码
陆亦可以为这个代码复用性过低,每次beforeFoo的参数个数有变化,还要一同修改下面的代码,因而改进了一下:
function beforeFoo(num,text){
        console.log('侯亮平知道陈海有床戏,一共'+num+'场,',text);
    }
    var fooOld = foo;
    foo = function(){
        beforeFoo.apply(this,arguments); //陆亦可修改了这里
        fooOld();
    }
    foo(30,'醒不过来'); //运行一下看看效果复制代码
刹车!陆亦可在她的代码里用到了apply!
咱们来分析一下她干了啥,完成了啥功能:
修改:把beforeFoo(num)改为beforeFoo.apply(this,arguments);
完成功能:beforeFoo能够任意修改参数个数,没必要再修改后续代码。复制代码
是否是挺神奇,咱们来分析一下:?
首先来看看beforeFoo.apply(this,arguments)中的this1this出如今新foo的内部;
2、foo的调用语句是foo(30'醒不过来'),是全局直接调用,找不到“.”;
根据我上一篇this教程,经过这两点,不难发现this指向window;

那么,根据本片文章前面提到过的,apply即“借”,beforeFoo.apply(this,arguments),
也就是this借用了beforeFoo方法,向谁借的?beforeFoo左边没有“.”,是全局调用,原来是向window借的!
而刚刚说过,此this指向window,这就好玩了:windowwindow借用了beforeFoo方法!

你说,那不就是beforeFoo直接调用吗,绕一圈干吗?别忘了还有arguments参数呢!
这么绕了一圈,在绕圈调用的过程当中,JS会解析arguments参数,自动用“,”帮你把参数分开传入beforeFoo方法,
之后不管你如何修改beforeFoo方法的参数个数,都不用再改剩余的代码了。
陆亦可利用这一点,巧妙的借助apply完成了代码的可用性提升。

PS:ES6新出的拓展符能够完成同样的效果:before.apply(this,arguments)能够写成before(...arguments);
请细细品味,发现道理都是想通的,有趣吧。

最后,侯亮平风骚的封装了代码,之后陈海不再怕不知道本身会演多少场床戏了。复制代码

小结

  • 看到call、apply出现,遵循着“借”的思想,再配合“改变this指向”,
  • XX.call(YY),那么这个“XX”就是被借的方法;
  • YY就是借方法的那个对象,this指向它;
  • XX是谁的?谁调用就是谁的,XX左边没有“.”,说明是全局调用,那就是window的。
  • 必定要作到“不找出究竟是向谁借的就不罢休”。

当你终于找到物主(究竟是“借”的谁的方法),接着理清this指向,你也就透彻的明白call和apply了。

PS:
欢迎转载,须要注明原址。
教程之间紧密联系,不懂的地方,请好好看下全系列教程目录
有没有你不懂的那个关键字在里面。
若是帮到你,别忘了给我一颗Star~
ui

相关文章
相关标签/搜索