在對(duì)js深入學(xué)習(xí)時(shí)芍锚,發(fā)現(xiàn)有很多地方使用了call、apply和bind方法蔓榄。在此之前自己在項(xiàng)目開(kāi)發(fā)中卻很少使用到并炮,但是發(fā)現(xiàn)在很多源碼如vue中經(jīng)常會(huì)出現(xiàn)其用法,以及在面試中經(jīng)常會(huì)被提及甥郑,所以特在此記錄并學(xué)習(xí)一下逃魄。
-函數(shù)用法
首先,我們來(lái)看一個(gè)例子:
var name = "李四";
var person = {
name:'張三',
fn:function(){
console.log(this.name)
}
}
person.fn();// 張三
對(duì)于上面代碼輸出的結(jié)果大家應(yīng)該不會(huì)有疑問(wèn)澜搅。再看下面:
var name = "李四";
var person = {
name:'張三',
fn:function(){
console.log(this.name)
}
}
var fn = person.fn;
fn(); //李四
這時(shí)候我們發(fā)現(xiàn)輸出的結(jié)果改變了伍俘,明明調(diào)用的是person的fn方法邪锌,為什么會(huì)輸出“李四”呢?
仔細(xì)觀察后我們不難發(fā)現(xiàn)問(wèn)題出在this身上癌瘾。
fn里的this指的是調(diào)用者觅丰,誰(shuí)調(diào)用了這個(gè)方法this就指向誰(shuí)。也就是說(shuō)妨退,在執(zhí)行person.fn()時(shí)妇萄,當(dāng)前調(diào)用者為person對(duì)象,此時(shí)person中有name屬性值為張三咬荷。而當(dāng)我們將person.fn賦值給新變量并直接調(diào)用時(shí)冠句,此時(shí)調(diào)用者為window對(duì)象,而window對(duì)象中name屬性值則為李四幸乒。
那怎么才能在第二種場(chǎng)景下也能輸出張三呢懦底?
1.call()
首先我們看下call()的用法:
var name = "李四";
var person = {
name:'張三',
fn:function(){
console.log(this.name)
}
}
var fn = person.fn;
fn.call(person); //張三
此時(shí)發(fā)現(xiàn)得到的正是我們想要的結(jié)果。
它的實(shí)現(xiàn)原理:通過(guò)call()方法改變了this(調(diào)用者)的指向罕扎。
通過(guò)call()方法調(diào)用fn方法并執(zhí)行聚唐,在call()方法中我們傳入了一個(gè)參數(shù):person,它就是調(diào)用者腔召,它可以將fn中的this由window對(duì)象改變成person對(duì)象拱层,因此在調(diào)用call()方法時(shí)我們將需要改變的this傳入即可。當(dāng)不傳參數(shù)或傳入null時(shí)宴咧,此時(shí)默認(rèn)this為window對(duì)象。
-支持傳入多個(gè)參數(shù)
當(dāng)person中方法帶有參數(shù)時(shí):
var name = "李四";
var person = {
name:'張三',
fn:function(gender,age){
console.log(this.name+','+gender+','+age)
}
}
var fn = person.fn;
fn.call(person,'男','25歲'); //張三径缅,男掺栅,25歲
傳入多個(gè)參數(shù)時(shí),第一個(gè)參數(shù)為調(diào)用者this,其后的參數(shù)為所調(diào)用的方法fn的參數(shù)纳猪。當(dāng)?shù)谝粋€(gè)參數(shù)傳null時(shí):
var name = "李四";
var person = {
name:'張三',
fn:function(gender,age){
console.log(this.name+','+gender+','+age)
}
}
var fn = person.fn;
fn.call(null,'男','25歲'); //李四氧卧,男,25歲氏堤,相當(dāng)于fn('男','25歲')
fn('男','25歲')沙绝;
第一個(gè)參數(shù)傳null時(shí)相當(dāng)于fn直接調(diào)用,不修改調(diào)用方this,但是一般在用call等方法時(shí)都是為了改變this指向鼠锈。
2.apply()
apply()方法和call()方法非常類(lèi)似:
var name = "李四";
var person = {
name:'張三',
fn:function(){
console.log(this.name)
}
}
var fn = person.fn;
fn.apply(person); //張三
調(diào)用時(shí)也一樣闪檬,但是在傳入多個(gè)參數(shù)時(shí)有區(qū)別:
var name = "李四";
var person = {
name:'張三',
fn:function(gender,age){
console.log(this.name+','+gender+','+age)
}
}
var fn = person.fn;
fn.apply(person,['男','25歲']); //張三,男购笆,25歲
在對(duì)person中fn傳入?yún)?shù)時(shí)粗悯,必須以數(shù)組形式傳入,否則會(huì)報(bào)錯(cuò)同欠。
3.bind()
bind()方法也用于改變調(diào)用者this样傍,不同于call()和apply()方法横缔,它再使用時(shí),方法不會(huì)被立即調(diào)用衫哥,且返回一個(gè)新函數(shù)(this改變后的)茎刚,這樣就可以在需要使用的時(shí)候進(jìn)行調(diào)用,也就是先將需要改變的this綁定到方法上撤逢。
var name = "李四";
var person = {
name:'張三',
fn:function(){
console.log(this.name)
}
}
var fn = person.fn;
var newFn = fn.bind(person);
newFn ();//張三
多個(gè)參數(shù):
var name = "李四";
var person = {
name:'張三',
fn:function(gender,age){
console.log(this.name+','+gender+','+age)
}
}
var fn = person.fn;
var newFn = fn.bind(person);
newFn('男','25歲'); //張三膛锭,男,25歲
當(dāng)然在var newFn = fn.bind(person)時(shí)我們也可以直接傳入其它參數(shù):var newFn = fn.bind(person,'男','25歲')笛质,不過(guò)一般不建議這么使用泉沾,在bind時(shí)只做this改變綁定操作,fn方法的參數(shù)在新函數(shù)newFn調(diào)用時(shí)傳入妇押。
-最后附上一個(gè)手寫(xiě)實(shí)現(xiàn)call()函數(shù):
Function.prototype.myCall = function(thisArg, ...args) {
const fn = Symbol('fn') // 聲明一個(gè)獨(dú)有的Symbol屬性, 防止fn覆蓋已有屬性
thisArg = thisArg || window // 若沒(méi)有傳入this, 默認(rèn)綁定window對(duì)象
thisArg[fn] = this // this指向調(diào)用call的對(duì)象,即我們要改變this指向的函數(shù)
const result = thisArg[fn](...args) // 執(zhí)行當(dāng)前函數(shù)
delete thisArg[fn] // 刪除我們聲明的fn屬性
return result // 返回函數(shù)執(zhí)行結(jié)果
}