你不知道的JavaScript基礎:call And apply

寫在前面

在JavaScript中call和apply這兩個函數是比較基礎的東西。因為一般的前端開發中用不到,所以初入Web前端的程序員們并不是很清楚他們的用法、聯系及區別。愛鉆研探索的程序員,或是已經在前端開發中漸入佳境者多少會聽說或用到這兩個函數,所以網上關于他們的介紹并不在少數。我在寫這篇之前也在網上搜索了一番,雖然大多數為復制粘貼相互轉載,但也有不少作者比較用心,寫的確有獨到之處。當然我對于call和apply以及他們的出身用途也都有自己的理解。

從何而來

站在面向對象的角度來說,在JavaScript中所有使用function關鍵字定義的方法或函數,都是Function對象(類)的一個實例對象。對象可以擁有自己的方法和屬性,而call和apply正是所有函數對象都具有的方法。請看下面的代碼段:

// 下面是使用兩種不同的方式定義函數了四個函數
> 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)");

//下面是對各個函數對象的call方法進行測試,通過代碼大家不難發現,
//所有函數對象的call方法都是相等的
> fun1.call === Function.prototype.call
< true
> fun2.call === Function.prototype.call
< true
> fun3.call === Function.prototype.call
< true
> fun4.call === Function.prototype.call

//下面是對各個函數對象的apply方法進行測試,通過代碼大家不難發現,
//所有函數對象的apply方法都是相等的
> fun1.apply=== Function.prototype.apply
< true
> fun2.apply=== Function.prototype.apply
< true
> fun3.apply=== Function.apply
< true
> fun4.apply=== Function.apply

//下面是對Function類(其實也是一個函數對象)的一些測試,
//通過代碼我們發現Function原型prototype的call方法與Function的call方法相等,
//且Function原型prototype的apply方法與Function的apply方法相等

> Function.prototype.call === Function.call
< true
> Function.prototype.call === Function.call
< true

通過上面的代碼段我們可以了解到,Function.prototype,Function,以及所有函數對象的call和apply方法都相等的,也就是說這些對象中的這兩個方法其實本質上是只兩個方法在被他們分別繼承反復重用了而已。

在JavaScript中,所有“函數對象”都是Function類的實例,無論這個函數對象是使用new Function()的方式定義,還是使用function關鍵字定義;包括Object類、甚至連Function類自身也都是Function類的對象,我們可以統稱他們為“函數對象”。而call和apply這兩個方法就是在Function.prototype中定義的方法,所以會在所有的函數對象中通過原型鏈從Function.prototype中得到繼承。這就是call和apply的由來。

作用

call和apply的作用是相同的,都是為了改變函數運行時的上下文。說上下文可能不太好理解。下面通過一段代碼來解釋:
好長的一段代碼,如果你已經理解了,可以勇敢點跳過你認為啰嗦的那部分代碼

//隨便定義兩個函數吧
function a(){
  console.log(this.val);
}

function b(){
  var str = "wfso";
  console.log(str);
}

//下面是對a和b兩個函數幾種不同的調用方式

// 通過下面的兩小段代碼,可以知道,函數的默認上下文是window,也可以說是global吧
> a();
< undefined

> var val = "val";
< a();
< val

// 對b的調用只是作為與a的對比
> b();
< wfso

// 創建一個對象w,然后把w當作a函數對象的call和apply方法的參數
// 改變a函數的執行上下文
> var w={val:"仵士杰"};
> a.call(w);
< 仵士杰
> a.apply(w);
< 仵士杰

// 為了有更清晰的結果,再創建一個對象v來做實驗吧
> var v={val:"www"};
> a.call(v);
< www
> a.apply(v);
< www

// 同樣作為對比,我們分別對b也做同樣的調用看看

> b.call(w);
< wfso
> b.apply(w);
< wfso

> b.call(v);
< wfso
> b.apply(v);
< wfso

如果我現在說,函數的執行上下文其實就是在函數中this關鍵字引用的對象,你應該不會有異議了吧。全局函數的默認上下文是window。而類中方法的默認上下文是調用這個方法的對象。如果你要改變這種默認的函數或方法執行時的上下文件,就需要通過函數對象的call或apply方法來實現了。

區別和用法 call OR apply

call 與 apply 在作用上是完全相同的,他們的區別體現調用時的參數傳遞上。

call 調用方法

調用call時如果需要參數傳遞,則需要把所有參數一一列出來。如下:

fun.call(ctx[,arg1[,arg2[,arg2[,……]]]]);

arg1,arg2,arg3是給fun函數傳的參數列表。

function a(str1,str2){console.log(this.val+"\n"+str1+"--"+str2)}
> v={val:"wfs"};
> a.call(v,"仵士杰","xt");
< wfs
< 仵士杰--xt

apply 調用方法

調用apply時如果需要參數傳遞,則需要把所有參數放到一個數組里,然后把所有參數組成的數組作為一個參數傳遞給apply方法。如下:

fun.apply(ctx[,arguments]);

arguments是一個數組,里面存儲的是給fun函數傳的參數列表。

function a(str1,str2){console.log(this.val+"\n"+str1+"--"+str2)}
> v={val:"wfs"};
> a.apply(v,["仵士杰","xt"]);
< wfs
< 仵士杰--xt

扎然而止

好了,上面的介紹已經是我能力的極限了,不想寫什么總結了。就這樣結束吧,打完收功!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容