手写系列 - call/apply/bind

85 阅读2分钟

先简单说下它们的作用?

它们三个的作用都是用于 改变函数运行时 this 的指向

function sayHello() {
  console.log(this);
}

const xmo = { name: "xiaoming" };
const xms = "xiaoming";

sayHello(); // Window
sayHello.call(xmo); // {name: 'xiaoming'}
sayHello.call(xms); // String {'xiaoming'} 原始类型被包装

它们之间有什么区别?

call 和 apply 的唯一区别是参数不同。call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组

如果这两个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。

  • call(thisArg, arg1, arg2, ...)
  • apply(thisArg, argsArray)

bind() 方法与上面两个的区别在于:bind 改变 this 指向之后不会立即调用,而是返回一个新函数。

  • bind(thisArg[, arg1[, arg2[, ...]]])

bind() 返回的新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。如果使用 new 运算符构造绑定函数,则忽略该值。当使用 bind 在 setTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者 thisArg 是 null 或 undefined,执行作用域的 this 将被视为新函数的 thisArg。

function sayHello() {
  console.log(this);
}

const xmo = { name: "xiaoming" };

sayHello(); // Window
const newSayHello = sayHello.bind(xmo);
newSayHello(); // {name: 'xiaoming'}

手写 call/apply/bind

call

/**
 * 注意点
 * 1. 非严格模式, 传入 null 或 undefined 为 thisArg 时,自动指向全局 window 对象
 * 2. 原始值会被包装
 */
Function.prototype._call = function (context, ...args) {
  context = Object(context ?? window);
  const fn = Symbol();
  context[fn] = this;
  const result = context[fn](...args);
  delete context[fn];
  return result;
};

apply

// 与 call() 方法类似,传参不同
Function.prototype._apply = function (context, args) {
  context = Object(context ?? window);
  const fn = Symbol();
  context[fn] = this;
  const result = context[fn](...(args ?? []));
  delete context[fn];
  return result;
};

bind

/**
 * 注意点:
 * 1. bind() 返回一个新函数。bind 的第一个参数为指定的 this 指向,其余参数将作为新函数的参数
 * 2. 如果使用 new 运算符构造绑定函数,则忽略 this(相当于 new 调用原方法,并传入其余参数)
 * 3. 如果 bind() 的参数为空,或者 thisArg 参数为 null 或 undefined ,执行作用域的 this 将被视为新函数的 thisArg
 * 4. 当使用 bind 在 setTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。
 */
Function.prototype._bind = function (thisArg, ...args1) {
  const self = this;
  const fbound = function (...args2) {
    return self.apply(this instanceof self ? this : thisArg, [
      ...args1,
      ...args2,
    ]);
  };
  const fNOP = function () {};
  fNOP.prototype = this.prototype;
  fbound.prototype = new fNOP();

  return fbound;
};