說說bind、call、apply的區別?並手寫實現一個bind的方法

王铁柱6發表於2024-11-21

bindcallapply 都是 JavaScript 中用於改變函式執行上下文(即函式內部的 this 指向)的方法,它們的主要區別在於:

  • call: 立即呼叫函式,並接受引數列表作為後續引數。
  • apply: 立即呼叫函式,並接受一個引數陣列作為引數。
  • bind: 建立一個新的函式,該函式的 this 值被繫結到指定的值,並可以預先設定一些引數。它不會立即執行原函式,而是返回一個新的函式,可以在之後呼叫。

更詳細的解釋:

  • call(thisArg, arg1, arg2, ...): 第一個引數 thisArg 是要繫結到函式的 this 值。後續引數 arg1, arg2 等是傳遞給函式的引數。

  • apply(thisArg, [arg1, arg2, ...]): 第一個引數 thisArgcall 一樣。第二個引數是一個陣列(或類陣列物件),包含傳遞給函式的引數。

  • bind(thisArg, arg1, arg2, ...): 第一個引數 thisArgcallapply。後續引數 arg1, arg2 等是預先設定給函式的引數。bind 返回一個新的函式,當這個新函式被呼叫時,預先設定的引數會先於新函式呼叫時傳入的引數傳遞給原函式。

手寫實現 bind 方法:

Function.prototype.myBind = function(thisArg, ...boundArgs) {
  const originalFunction = this; // 儲存原函式

  // 返回一個新函式
  return function(...args) {
    // 合併預設引數和呼叫時傳入的引數
    const allArgs = boundArgs.concat(args);

    // 使用 apply 呼叫原函式,並設定 this 值和引數
    return originalFunction.apply(thisArg, allArgs);
  };
};


// 測試用例:
function greet(greeting, name) {
  console.log(`${greeting}, ${name}! I am ${this.title}`);
}

const person = {
  title: "Professor"
};

const greetPerson = greet.myBind(person, "Hello");
greetPerson("John"); // 輸出: Hello, John! I am Professor

const greetPerson2 = greet.myBind(person, "Hi", "Jane");
greetPerson2(); // 輸出: Hi, Jane! I am Professor

const greetPerson3 = greet.myBind(person);
greetPerson3("Good morning", "David"); // 輸出: Good morning, David! I am Professor


關鍵點解釋:

  1. 儲存原函式: const originalFunction = this; 儲存對原函式的引用,以便在新函式內部呼叫。

  2. 返回新函式: bind 的核心是返回一個新函式,這個新函式會在之後被呼叫。

  3. 合併引數: boundArgs.concat(args) 將預設引數 boundArgs 和呼叫新函式時傳入的引數 args 合併成一個陣列 allArgs

  4. 使用 apply 呼叫原函式: originalFunction.apply(thisArg, allArgs) 使用 apply 方法呼叫原函式,並將 this 值設定為 thisArg,引數設定為合併後的引數陣列 allArgs

這個手寫實現涵蓋了 bind 的核心功能。一些更完善的實現還會處理一些邊緣情況,例如:

  • new 運算子: 當使用 new 運算子呼叫繫結函式時,繫結的 this 值會被忽略。
  • 返回函式的 prototype: 需要正確設定返回函式的 prototype 屬性。

這個更完善的版本,可以參考MDN的polyfill:

if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to throwing a TypeError in IE6-8
      // since it doesn't support accessing the 'caller' property
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }

    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          return fToBind.apply(this instanceof fNOP
                 ? this
                 : oThis || this,

相關文章