一文瞭解JavaScript中call/apply/bind的使用

前言

在JavaScript中,經常會通過call / apply / bind 函數來改變this的指向,詳情可看一文帶你瞭解this指向,今天我們來研究下這三個函數的實現。

1. call

call()函數是什麼?

call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。也就是說call() 改變瞭this指向並執行瞭函數

1.1 語法

func.call(thisArg, arg1, arg2, ...)
// thisArg為在 func 函數運行時使用的 this 值
// arg1, arg2等為指定的參數列表
// 其返回值為調用有指定 this 值和參數的函數的結果

1.2 流程圖

一般來說,我們要模擬實現call,可以分為以下幾個步驟:

  • 將函數設置為對象的屬性, 當對象為null或undefined, 設為window對象
  • 取出函數執行所需參數,執行該函數
  • 如果函數存在返回值,在返回後刪除該函數

以下就是call()方法實現的流程圖:

1.3 代碼實現

Function.prototype.call = function (thisArg, ...argsArray) {
  if (typeof this !== "function") {
    throw new TypeError(
      "Function.prototype.call was called on which is not a function"
    );
  }

  if (thisArg === undefined || thisArg === null) {
    thisArg = window;
  } else {
    thisArg = Object(thisArg);
  }

  // 將 func 放入 thisArg 內,這樣在調用 thisArg[func] 時 this 自然就指向瞭 thisArg
  const func = Symbol("func");
  thisArg[func] = this;

  let result;

  if (argsArray.length) {
    result = thisArg[func](...argsArray);
  } else {
    result = thisArg[func]();
  }

  delete thisArg[func];

  return result;
};

2. apply

apply()函數是什麼?

apply() 方法調用一個具有給定 this 值的函數,以及以一個數組(或一個類數組對象)的形式提供的參數。同call()的功能,改變this指向的同時執行瞭函數。

2.1 語法

func.apply(thisArg, [argsArray]);
// thisArg為在 func 函數運行時使用的 this 值
// arg1, arg2等為指定的參數列表
// 其返回值為調用有指定 this 值和參數的函數的結果

2.2 流程圖

apply()方法實現的流程基本與call的實現流程沒有太多差異,隻需要對函數參數數組進行判斷展開即可。

以下是apply()函數的流程圖:

2.3 代碼實現

Function.prototype.apply = function (thisArg, argsArray) {
  if (typeof this !== "function") {
    throw new TypeError(
      "Function.prototype.apply was called on which is not a function"
    );
  }

  if (thisArg === undefined || thisArg === null) {
    thisArg = window;
  } else {
    thisArg = Object(thisArg);
  }

  // 將 func 放入 thisArg 內,這樣在調用 thisArg[func] 時 this 自然就指向瞭 thisArg
  const func = Symbol("func");
  thisArg[func] = this;

  let result;

  if (argsArray && typeof argsArray === "object" && "length" in argsArray) {
    // 此處使用 Array.from 包裹讓其支持形如 { length: 1, 0: 1 } 這樣的類數組對象,直接對 argsArray 展開將會執行出錯
    result = thisArg[func](...Array.from(argsArray));
  } else if (argsArray === undefined || argsArray === null) {
    result = thisArg[func]();
  } else {
    throw new TypeError("CreateListFromArrayLike called on non-object");
  }
  delete thisArg[func];
  return result;
};

3. bind

💡bind() 函數是什麼?

bind() 方法創建一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定為 bind() 的第一個參數,而其餘參數將作為新函數的參數,供調用時使用。

3.1 語法

func.bind(thisArg[, arg1[, arg2[, ...]]])
// thisArg 為調用綁定函數時作為 this 參數傳遞給目標函數的值, 如果使用 new 運算符構造綁定函數,忽略該值
// arg1, arg2為當目標函數被調用時,被預置入綁定函數的參數列表中的參數。
// 其返回值為一個原函數的拷貝,並擁有指定的 this 值和初始參數

3.2 流程圖

想要實現bind函數,即需實現兩個特點:一為返回一個函數;二為可以傳入參數。

所以我們可從以下幾點入手:

  • 通過使用`call`或者`apply`實現 `this`的指定;
  • 實現在`bind`的時候可以傳參,在執行返回函數時傳參;
  • 判斷是否使用 `new`操作符來確定`this`指向。

話不多說,下面就是bind函數的流程圖:

3.3 代碼實現

Function.prototype.bind = function (thisArg, ...argsArray) {
  if (typeof this !== "function") {
    throw new TypeError(
      "Function.prototype.bind was called on which is not a function"
    );
  }
  if (thisArg === undefined || thisArg === null) {
    thisArg = window;
  } else {
    thisArg = Object(thisArg);
  }
  const func = this;
  const bound = function (...boundArgsArray) {
    let isNew = false;

    // 如果 func 不是構造器,直接使用 instanceof 將出錯,所以需要用 try...catch 包裹
    try {
      isNew = this instanceof func;
    } catch (error) {}

    return func.apply(isNew ? this : thisArg, argsArray.concat(boundArgsArray));
  };

  const Empty = function () {};
  Empty.prototype = this.prototype;
  bound.prototype = new Empty();

  return bound;
};

4.全文總結

call、apply與bind有什麼區別?

  • calll、apply 與 bind 都用於this綁定,但 call、apply 函數在改變this指向的同時還會執行函數;而 bind 函數在改變this後返回一個全新的綁定函數。
  • bind 屬於硬綁定,返回的綁定函數的this指向不能再通過 bind、apply 或 call 修改,即this被永久綁定;call 與 apply 隻適用於當前調用,一次調用後就結束。
  • call 和 apply 功能完全相同,但call 從第二個參數後的所有參數都是原函數的參數;而 apply 隻接受兩個參數,第二個參數必須是數組,該數組包含著原函數的參數列表。

到此這篇關於一文瞭解JavaScript中call/apply/bind的使用的文章就介紹到這瞭,更多相關JavaScript call,apply,bind內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: