寫在前面
在JavaScript中call和apply這兩個函數(shù)是比較基礎(chǔ)的東西害淤。因為一般的前端開發(fā)中用不到平窘,所以初入Web前端的程序員們并不是很清楚他們的用法炉旷、聯(lián)系及區(qū)別楷兽。愛鉆研探索的程序員地熄,或是已經(jīng)在前端開發(fā)中漸入佳境者多少會聽說或用到這兩個函數(shù),所以網(wǎng)上關(guān)于他們的介紹并不在少數(shù)芯杀。我在寫這篇之前也在網(wǎng)上搜索了一番端考,雖然大多數(shù)為復(fù)制粘貼相互轉(zhuǎn)載,但也有不少作者比較用心揭厚,寫的確有獨(dú)到之處却特。當(dāng)然我對于call和apply以及他們的出身用途也都有自己的理解。
從何而來
站在面向?qū)ο蟮慕嵌葋碚f筛圆,在JavaScript中所有使用function關(guān)鍵字定義的方法或函數(shù)裂明,都是Function對象(類)的一個實(shí)例對象。對象可以擁有自己的方法和屬性太援,而call和apply正是所有函數(shù)對象都具有的方法漾岳。請看下面的代碼段:
// 下面是使用兩種不同的方式定義函數(shù)了四個函數(shù)
> function fun1(str){console.log("fun1 say "+str)}
> fun2 = new Function("str","console.log('fun2 say '+str)");
> function fun3(a,b){console.log(a+b)}
> fun4 = new Function("a","b","console.log(a+b)");
//下面是對各個函數(shù)對象的call方法進(jìn)行測試,通過代碼大家不難發(fā)現(xiàn)粉寞,
//所有函數(shù)對象的call方法都是相等的
> fun1.call === Function.prototype.call
< true
> fun2.call === Function.prototype.call
< true
> fun3.call === Function.prototype.call
< true
> fun4.call === Function.prototype.call
//下面是對各個函數(shù)對象的apply方法進(jìn)行測試,通過代碼大家不難發(fā)現(xiàn)左腔,
//所有函數(shù)對象的apply方法都是相等的
> fun1.apply=== Function.prototype.apply
< true
> fun2.apply=== Function.prototype.apply
< true
> fun3.apply=== Function.apply
< true
> fun4.apply=== Function.apply
//下面是對Function類(其實(shí)也是一個函數(shù)對象)的一些測試唧垦,
//通過代碼我們發(fā)現(xiàn)Function原型prototype的call方法與Function的call方法相等,
//且Function原型prototype的apply方法與Function的apply方法相等
> Function.prototype.call === Function.call
< true
> Function.prototype.call === Function.call
< true
通過上面的代碼段我們可以了解到液样,F(xiàn)unction.prototype振亮,F(xiàn)unction巧还,以及所有函數(shù)對象的call和apply方法都相等的,也就是說這些對象中的這兩個方法其實(shí)本質(zhì)上是只兩個方法在被他們分別繼承反復(fù)重用了而已坊秸。
在JavaScript中麸祷,所有“函數(shù)對象”都是Function
類的實(shí)例,無論這個函數(shù)對象是使用new Function()
的方式定義褒搔,還是使用function
關(guān)鍵字定義阶牍;包括Object
類、甚至連Function
類自身也都是Function
類的對象星瘾,我們可以統(tǒng)稱他們?yōu)椤昂瘮?shù)對象”走孽。而call和apply這兩個方法就是在Function.prototype中定義的方法,所以會在所有的函數(shù)對象中通過原型鏈從Function.prototype中得到繼承琳状。這就是call和apply的由來磕瓷。
作用
call和apply的作用是相同的,都是為了改變函數(shù)運(yùn)行時的上下文念逞。說上下文可能不太好理解困食。下面通過一段代碼來解釋:
好長的一段代碼,如果你已經(jīng)理解了翎承,可以勇敢點(diǎn)跳過你認(rèn)為啰嗦的那部分代碼
//隨便定義兩個函數(shù)吧
function a(){
console.log(this.val);
}
function b(){
var str = "wfso";
console.log(str);
}
//下面是對a和b兩個函數(shù)幾種不同的調(diào)用方式
// 通過下面的兩小段代碼硕盹,可以知道,函數(shù)的默認(rèn)上下文是window审洞,也可以說是global吧
> a();
< undefined
> var val = "val";
< a();
< val
// 對b的調(diào)用只是作為與a的對比
> b();
< wfso
// 創(chuàng)建一個對象w莱睁,然后把w當(dāng)作a函數(shù)對象的call和apply方法的參數(shù)
// 改變a函數(shù)的執(zhí)行上下文
> var w={val:"仵士杰"};
> a.call(w);
< 仵士杰
> a.apply(w);
< 仵士杰
// 為了有更清晰的結(jié)果,再創(chuàng)建一個對象v來做實(shí)驗吧
> var v={val:"www"};
> a.call(v);
< www
> a.apply(v);
< www
// 同樣作為對比芒澜,我們分別對b也做同樣的調(diào)用看看
> b.call(w);
< wfso
> b.apply(w);
< wfso
> b.call(v);
< wfso
> b.apply(v);
< wfso
如果我現(xiàn)在說仰剿,函數(shù)的執(zhí)行上下文其實(shí)就是在函數(shù)中this關(guān)鍵字引用的對象,你應(yīng)該不會有異議了吧痴晦。全局函數(shù)的默認(rèn)上下文是window南吮。而類中方法的默認(rèn)上下文是調(diào)用這個方法的對象。如果你要改變這種默認(rèn)的函數(shù)或方法執(zhí)行時的上下文件誊酌,就需要通過函數(shù)對象的call或apply方法來實(shí)現(xiàn)了部凑。
區(qū)別和用法 call OR apply
call 與 apply 在作用上是完全相同的,他們的區(qū)別體現(xiàn)調(diào)用時的參數(shù)傳遞上碧浊。
call 調(diào)用方法
調(diào)用call時如果需要參數(shù)傳遞涂邀,則需要把所有參數(shù)一一列出來。如下:
fun.call(ctx[,arg1[,arg2[,arg2[,……]]]]);
arg1,arg2,arg3是給fun函數(shù)傳的參數(shù)列表箱锐。
function a(str1,str2){console.log(this.val+"\n"+str1+"--"+str2)}
> v={val:"wfs"};
> a.call(v,"仵士杰","xt");
< wfs
< 仵士杰--xt
apply 調(diào)用方法
調(diào)用apply時如果需要參數(shù)傳遞比勉,則需要把所有參數(shù)放到一個數(shù)組里,然后把所有參數(shù)組成的數(shù)組作為一個參數(shù)傳遞給apply方法。如下:
fun.apply(ctx[,arguments]);
arguments是一個數(shù)組浩聋,里面存儲的是給fun函數(shù)傳的參數(shù)列表观蜗。
function a(str1,str2){console.log(this.val+"\n"+str1+"--"+str2)}
> v={val:"wfs"};
> a.apply(v,["仵士杰","xt"]);
< wfs
< 仵士杰--xt
扎然而止
好了,上面的介紹已經(jīng)是我能力的極限了衣洁,不想寫什么總結(jié)了墓捻。就這樣結(jié)束吧,打完收功坊夫!