bind、call、apply 的区别及实现原理

458 阅读3分钟

学过 js 的朋友都知道 bind、call、apply 都是 function 原型上的方法,每一个函数作为 function 的实例都可以调用这些方法来改变函数中 this 的指向,那么它们是如何使用的呢,本文着重帮助你理解它们的使用区别及如何用原生 js 实现这三个方法。

bind、call、apply 的使用区别

传递参数类型的区别

在使用 bind、call、apply 的时候,我们不仅可以传递一个上下文作为 this 的新指向,也可以传递一些参数在函数中进行使用。

使用 bind、call 的时候,我们需要把参数一个个添加到上下文后面。

const func = function() {};
func.call(context, '参数1', '参数2', ...); 
func.bind(context, '参数1', '参数2', ...)(); 

使用 apply 的时候,我们只能把参数以数组的形式添加到上下文后面。

const func = function() {};
func.apply(context, ['参数1', '参数2', ...]);

tip:这并不意味着使用 bind、call 时不能以数组的方式传递参数,我们可以使用扩展运算符来传递数组。

const func = function() {};
let arr = ['参数1', '参数2', ...];
func.call(context, ...arr);
func.bind(context, ...arr)();

ps:一般来说,call 的性能要比 apply 好那么一点。

返回值类型的区别

如果仔细看上面的几行代码的话,可以发现在每个 bind 方法后面都加了一对小括号,这是为什么呢? 这是因为 bind 方法在执行后的返回值是一个新函数(可以定义变量对其接收),而 call、apply 执行后返回的是执行结果。所以我们需要加一对小括号来执行新函数。

手写 bind、call、apply 方法

具体实现步骤请看注释

// 实现 js bind 方法
Function.prototype.myBind = function(context) {
  // 判断 this 指向,如果不为函数则报错
  if (typeof this !== 'function') {
    throw new TypeError('error');
  }
  let _this = this; // 保存执行函数
  let args = [...arguments].slice(1); // 取出执行函数的剩余参数列表
  return function() {
    _this.apply(context, args.concat(...arguments)); // 改变 this 指向并拼接参数列表
  }
}
// 实现 js call 方法
Function.prototype.myCall = function(context) {
  // 判断 this 指向,如果不为函数则报错
  if (typeof this !== 'function') {
    throw new TypeError('error');
  }
  let context = context || window; // 如果不传入参数,默认将 this 指向 window 对象
  context.fn = this; // 将执行函数赋值为上下文对象的属性
  let args = [...arguments].slice(1); // 取出执行函数的剩余参数列表
  let result = context.fn(...args); // 执行函数
  delete context.fn; // 删除上下文属性,避免内存泄漏
  return result; // 返回执行结果
}
// 实现 js apply 方法
Function.prototype.myApply = function(context) {
  // 判断 this 指向,如果不为函数则报错
  if (typeof this !== 'function') {
    throw new TypeError('error');
  }
  let context = context || window; // 如果不传入参数,默认将 this 指向 window 对象
  context.fn = this; // 将执行函数赋值为上下文对象的属性
  let result;
  // 判断是否有剩余参数,如果有则带参执行,没有则直接执行
  if (arguments[1]) {
    result = context.fn(...arguments[1]);
  } else {
    result = context.fn();
  }
  delete context.fn; // 删除上下文属性,避免内存泄漏
  return result; // 返回执行结果
}

你们的阅读是对我最大的鼓励!