天天看點

前端學習筆記(二):call 、apply和bind

一、了解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)

繼續閱讀