這個系列的教程我一開始是寫在github上的,
可是以爲放到掘金來可讓更多須要的人看到,
就搬到掘金專欄上啦,
若是以爲本教程對你有幫助,請點這裏去github上給我一顆Star~
教程目錄也在github上哈~javascript
下面開始第三篇:java
想當年我仍是個小白的時候,看到call和apply,那都是一臉懵逼啊!
再加上參數內部this,arguments什麼的,虐的我不要不要的,一度產生厭學心理。
的確,這倆方法對初學者不夠友好...git
可是!做爲半個老鳥,如今看到call啊什麼apply啊什麼的,也就微微一笑了。
想當初茅塞頓開的時候,那內心叫一個痛快,如今就把開竅的過程分享出來。github
先說一下call和apply的區別,你在徹底不懂倆函數是幹嗎的狀況下,你只要記住:
call和apply的功能是徹底同樣的,只是第二個參數不同;
call能夠接收無限多個參數,apply只接收倆參數,而且第二個參數只能是argument。
「而它們一樣的第一個參數,就是新的this指向!」
你先不用管引號裏的話說明了什麼,腦子裏默記下這句話就行。
好了,如今,不要多想,往下看。app
我在實際應用中,最經常使用的就是用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方法完成遍歷。複製代碼
好比有個需求,須要作到每次調用先前別人寫好的方法時,先在前面運行咱們添加的代碼:
下面的代碼不必定是最好的實現本需求的代碼,但能夠演示apply的應用。
生動的具體化一下:學習
function foo(){
console.log('我是陳海,我拍牀戲去了');
}
foo();複製代碼
如今侯亮平接手的反貪局接管了代碼,
需求是,不改變陳海寫的代碼的狀況下,在每次調用陳海寫的代碼時先打印一些話。複製代碼
function beforeFoo(num){
console.log('侯亮平知道陳海有牀戲,一共'+num+'場');
}
var fooOld = foo;
foo = function(num){
beforeFoo(num); //這裏將會被陸亦可修改
fooOld();
}
foo(30); //運行一下看看效果複製代碼
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)中的this:
1、this出如今新foo的內部;
2、foo的調用語句是foo(30,'醒不過來'),是全局直接調用,找不到「.」;
根據我上一篇this教程,經過這兩點,不難發現this指向window;
那麼,根據本片文章前面提到過的,apply即「借」,beforeFoo.apply(this,arguments),
也就是this借用了beforeFoo方法,向誰借的?beforeFoo左邊沒有「.」,是全局調用,原來是向window借的!
而剛剛說過,此this指向window,這就好玩了:window向window借用了beforeFoo方法!
你說,那不就是beforeFoo直接調用嗎,繞一圈幹嗎?別忘了還有arguments參數呢!
這麼繞了一圈,在繞圈調用的過程當中,JS會解析arguments參數,自動用「,」幫你把參數分開傳入beforeFoo方法,
之後不管你如何修改beforeFoo方法的參數個數,都不用再改剩餘的代碼了。
陸亦可利用這一點,巧妙的藉助apply完成了代碼的可用性提升。
PS:ES6新出的拓展符能夠完成同樣的效果:before.apply(this,arguments)能夠寫成before(...arguments);
請細細品味,發現道理都是想通的,有趣吧。
最後,侯亮平風騷的封裝了代碼,之後陳海不再怕不知道本身會演多少場牀戲了。複製代碼
PS:
歡迎轉載,須要註明原址。
教程之間緊密聯繫,不懂的地方,請好好看下全系列教程目錄,
有沒有你不懂的那個關鍵字在裏面。
若是幫到你,別忘了給我一顆Star~
ui