js裡對call的定義:
呼叫一個物件的一個方法,以另一個物件替換當前物件。call方法接受以下引數(obj,argument1,argument2,...)。
我們用call寫一個小demo:
如上圖我們就可以很直觀的理解js對call的定義。呼叫window物件下的fn方法,用obj物件替換了window物件。可能這句話理解還是有些歧義,我們之後再解釋。
為什麼說obj物件替換了window物件?見圖:
我們從程式碼裡看到直接呼叫fn
輸出window
下的name
。因為fn
在window
物件下,kack
也在window
下。
當我們用call
的時候,把window
物件替換成了obj
,所以輸出了obj的name:'tom'
。
對於call的實現分析:
- call改變了this指向;
- 呼叫的方法執行了;
- call接受多個引數;
問題一:我們如何改變一個方法的this的指向呢? 問題二:如何去改變this指向,有去呼叫這個方法? 問題三:如何接受多個引數?再去執行?
可以看到我們給一個物件新增一個fn1屬性,並把fn方法賦給這個屬性,就相當於改變了fn裡的this指向!然後再呼叫obj.fn1這個屬性,不就執行了fn方法嗎?是不是很簡單~
那多出來的fn1屬性怎麼辦?也很簡單,把這個物件的屬性刪除不就可以麼。。。那多個引數怎麼辦?我們獲取一個方法的引數可以通過arguments來獲取,arguments是一個偽陣列物件。然後遍歷這個物件把屬性再放到一個陣列裡,這樣就可以取出來了。
最後一個問題:得到引數陣列後怎麼放到方法裡去執行呢?我們要用到eval()
函式,eval()
函式可計算某個字串,並執行其中的的 JavaScript 程式碼。
我們先按上面的分析實現call:
Function.prototype._call = function(){
var args = [];
var obj = arguments[0] || window; // call 如果第一個引數傳null,則這個物件是window
for(var i=1, len = arguments.length; i < len; i++){
// 因為執行一個函式傳參一般都是傳string型別,所以此處需要用字串拼接,之後用eval去解析這個字串。
args.push('arguments['+ i +']'');
}
obj.fn = this; // this就是呼叫_call的函式
// args = ['arguments[1]', 'arguments[2]', 'arguments[3]', ...];
// 此處執行eval時,會先呼叫args.toString(),然後執行eval時會從對應的arguments物件中取引數。
eval('obj.fn(' + args +')');
delete obj.fn;
};
複製程式碼
執行以上程式碼:
大功告成,可以看出我們實現了call方法!