一、了解apply(),call(),bind()
JavaScript中的每一個Function對象都有一個bind()、apply()方法和一個call()方法,它們的文法分别為:
bind()方法
fun.bind(thisArg[, arg1[, arg2[, ...]]])
apply()方法
function.apply(thisObj[, argArray])
call()方法
function.call(thisObj[, arg1[, arg2[, [,...argN]]]]);`
它們各自的定義:
apply:調用一個對象的一個方法,用另一個對象替換目前對象。例如:B.apply(A, arguments);即A對象應用B對象的方法。
call:調用一個對象的一個方法,用另一個對象替換目前對象。例如:B.call(A, args1,args2);即A對象調用B對象的方法。
bind:他是直接改變這個函數的this指向并且傳回一個新的函數,之後再次調用這個函數的時候this都是指向bind綁定的第一個參數。bind傳參方式跟call方法一緻。
它們的共同之處:
都“可以用來代替另一個對象調用一個方法,将一個函數的對象上下文從初始的上下文改變為由thisObj指定的新對象”。
它們的不同之處:
apply:最多隻能有兩個參數——新this對象和一個數組argArray。如果給該方法傳遞多個參數,則把參數都寫進這個數組裡面,當然,即使隻有一個參數,也要寫進數組裡。如果argArray不是一個有效的數組或arguments對象,那麼将導緻一個TypeError。如果沒有提供argArray和thisObj任何一個參數,那麼Global對象将被用作thisObj,并且無法被傳遞任何參數。
call:它可以接受多個參數,第一個參數與apply一樣,後面則是一串參數清單。這個方法主要用在js對象各方法互相調用的時候,使目前this執行個體指針保持一緻,或者在特殊情況下需要改變this指針。如果沒有提供thisObj參數,那麼 Global 對象被用作thisObj。
bind:傳回對應函數, 便于稍後調用。
實際上,apply和call的功能是一樣的,隻是傳入的參數清單形式不同。
function add(a,b){
return a+b;
}
function sub(a,b){
return a-b;
}
var a1 = add.apply(sub,[4,2]);  //sub調用add的方法
var a2 = sub.apply(add,[4,2]);
alert(a1); //6
alert(a2); //2
var a3 = add.call(sub,4,2);
alert(a3); //6
var a4 =add.bind(sub,4,2);
console.log(a4)
a4() //6
複制代碼
二、手寫 call、apply 及 bind 函數
首先從以下幾點來考慮如何實作這幾個函數
- 不傳入第一個參數,那麼上下文預設為 window
- 改變了 this 指向,讓新的對象可以執行該函數,并能接受參數
那麼我們先來實作 call
Function.prototype.myCall = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
context = context || window
context.fn = this
const args = [...arguments].slice(1)
const result = context.fn(...args)
delete context.fn
return result
}
複制代碼
以下是對實作的分析:
首先 context 為可選參數,如果不傳的話預設上下文為 window;
接下來給 context 建立一個 fn 屬性,并将值設定為需要調用的函數 因為 call 可以傳入多個參數作為調用函數的參數,是以需要将參數剝離出來;
然後調用函數并将對象上的函數删除;
以上就是實作 call 的思路,apply 的實作也類似,差別在于對參數的處理,是以就不一一分析思路了
Function.prototype.myApply = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
context = context || window
context.fn = this
let result
// 處理參數和 call 有差別
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
複制代碼
bind 的實作對比其他兩個函數略微地複雜了一點,因為 bind 需要傳回一個函數,需要判斷一些邊界問題,以下是 bind 的實作
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
const _this = this
const args = [...arguments].slice(1)
// 傳回一個函數
return function F() {
// 因為傳回了一個函數,我們可以 new F(),是以需要判斷
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
複制代碼
以下是對實作的分析:
前幾步和之前的實作差不多,就不贅述了 bind 傳回了一個函數,對于函數來說有兩種方式調用,一種是直接調用,一種是通過 new 的方式,
我們先來說直接調用的方式
對于直接調用來說,這裡選擇了 apply 的方式實作,但是對于參數需要注意以下情況:因為 bind 可以實作類似這樣的代碼 f.bind(obj, 1)(2),是以我們需要将兩邊的參數拼接起來,于是就有了這樣的實作 args.concat(...arguments)