在瞭解call()
和apply()
原理之前,我們必須對this
的作用和使用方法有所瞭解,如果你熟悉this
的用法,那麼請直接往下看。
call方法:
語法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
定義:呼叫一個物件的一個方法,以另一個物件替換當前物件。
說明:call 方法可以用來代替另一個物件呼叫一個方法。call 方法可將一個函式的物件上下文從初始的上下文改變為由 thisObj 指定的新物件。 如果沒有提供 thisObj 引數,那麼 Global 物件被用作 thisObj。
apply方法:
語法:apply([thisObj[,argArray]])
定義:應用某一物件的一個方法,用另一個物件替換當前物件。
說明:如果 argArray 不是一個有效的陣列或者不是 arguments 物件,那麼將導致一個 TypeError。
如果沒有提供 argArray 和 thisObj 任何一個引數,那麼 Global 物件將被用作 thisObj, 並且無法被傳遞任何引數。
call()
和apply()
的作用十分相似,只是引數型別上的差別,以適應不同的使用場景。它們都是為了改變函式執行時的 context(上下文)而存在的,再說的直白一點,就是為了改變函式內部 this 的指向。
舉例說明
我們有一句很經典的諺語,說的是:龍生龍,鳳生鳳,老鼠生來會打洞,這從遺傳上解釋是,動物的某些行為有可能是由一系列基因所調控的,但是,注意,我們偏偏想讓龍來打洞呢,該如何去實現?下面將圍繞這個話題來解釋call()
和apply()
的原理。
var dragon = {
name : 'foo'
// other attribute
}
var mouse = {
name : 'tom',
makeHole : function(where){
console.log(this.name + ' is making a hole in the ' + where)
}
// other attribute
}
mouse.makeHole.call(dragon,'hill')
複製程式碼
執行上面程式碼後會在控制檯上列印出:
可以看出,我們宣告瞭一個dragon
的物件,我們並沒有賦予它打洞
的功能,但是我們使用call()
繼承了mouse
的方法,就可以做到mouse
函式所能做到的事情。
這到底是怎麼做到的呢?讓我們來看看call()
的引數:
第一個是一個物件,這個物件將代替Function
類裡原本的this
物件,我們傳入的是this
,記住,這個this
在makeHole
函式裡指的是未來將要例項化這個函式的物件(我知道這有些拗口),當宣告瞭dragon
的時候,這個this
指的就是dragon
。除了第一個引數,後面所有的引數都是傳給父函式本身使用的引數。
而apply()
和call()
功能幾乎一樣,唯一的區別就是apply()
第二個引數只能是陣列,這個陣列將作為引數傳給原函式的引數列表arguments
。
模擬實現call()函式
call()
函式是什麼樣的原理呢?我們用一個例項來幫助理解。
//建立Dragon
function Dragon(name) {
this.name = name;
}
//建立一個說話的函式
function say(content) {
console.log(this.name + ' : ' + content)
}
//模擬原生call函式
Function.prototype.myCall = function(context) {
context = context || window;
var args = [];
context.fn = this;
for (var i = 1; i < arguments.length; i++) {
args.push(arguments[i]);
};
context.fn(...args);
delete context.fn;
};
//例項化一個名字為'foo'的龍
var foo = new Dragon('foo')
//讓foo說話
say.myCall(foo, 'I can talk!')
複製程式碼
上面的程式碼很容易理解,唯一的困難點在於理解在原型鏈上的myCall
函式
我們來分析實現的步驟:
- 做一個多場景適配,當
myCall
函式沒有接收到引數時,context
對應的是window
物件 - 建立一個空陣列,用於接收形參。
- 繫結
this
,這裡的this
代表的就是上下文中的say
函式。 for
迴圈將引數新增到args
陣列,迴圈從1開始是因為第0位是foo
物件,並非我們需要的引數- 執行函式,並將
args
陣列作為rest引數傳入,這裡是ES6的寫法,不熟悉的同學參見阮一峰老師的rest 引數文件 - 刪除函式
列印結果為:
可以看到,這裡我們實現了讓一個叫做foo
的龍說話!
apply()
函式實現方式同樣類似,可以修改上述例子實現,主要是在引數一部分做處理。