如何模拟实现一个call、apply、bind函数

首先强烈推荐这位大佬的文章,写的至关棒,后续讨论内容的也是你来我往,分析的很透彻。
JavaScript深刻之call和apply的模拟实现
JavaScript深刻之bind的模拟实现前端

call, apply, bind这三个方法均可以改变函数内部this指向。区别是call, apply是当即指向该函数,而bind是返回一个新的函数,用于下次调用。git

其中,call和apply的区别是传递函数参数的方式不一样,call是一个一个传入,例如call(this, arg1, arg2, arg3)这样。可是apply是经过一个数组传递,好比apply(this, [arg1, arg2, arg3])。github

call模拟实现

首先咱们实现绑定this功能。数组

// 好比咱们有一个foo函数
function getName() {
  return this.name;
}

// 还有一个wechat对象
const wechat = { name: 'fedaily' };

// 咱们但愿实现
getName.call(wechat); // fedaily

那么在call内部如何设计可以实现这个功能呢,咱们能够联想到将getName方法变成wechat对象里的一个方法,而后经过wechat.getName(),是否是就能够实现这个效果了。微信

咱们来实践一下:app

Function.prototype.call = function (context) {
  context.fn = this;
  context.fn();
  delete context.fn;
}

wechatgetName这个为例,这里的this即getName,context即wechat。
咱们将getName赋值给wechat对象的fn熟悉,而后经过wechat对象调用,最后删除这个fn属性。(实际中咱们确定不能用fn这个名字,避免和对象本来重复,咱们能够用Symbol实现)函数

而后咱们绑定this的功能就实现。还有传递参数,这个也好办。this

Function.prototype.call = function () {
  const [ctx, ...args] = arguments;
  ctx.fn = this;
  ctx.fn(...args);
  delete ctx.fn;
}
// 这里其实用了ES6的语法,可是主要为了更好的说明整个实现过程,理解原理就好

这里经过arguments对象去获取传进来的参数以及thisspa

而后考虑到函数多是有返回值的,因此ctx.fn()的执行结果也须要返回。同时this可能为null,那么咱们须要将this指向window。prototype

因此咱们再来调整一下:

Function.prototype.call = function () {
  const [ctx, ...args] = arguments;
  ctx.fn = this || window;
  const result = ctx.fn(...args);
  delete ctx.fn;
  return result;
}

这样,咱们就模拟实现了一个call方法。

apply模拟实现

接上文,咱们再来实现一个apply就很容易了。咱们直接上代码:

Function.prototype.apply = function () {
  const [ctx, args] = arguments; // args区别
  ctx.fn = this || window;
  const result = ctx.fn(...args);
  delete ctx.fn;
  return result;
}

bind模拟实现

bind会返回一个新函数,新函数执行时的this是bind方法的第一个参数。

根据这些特征,咱们能够想到使用apply帮助实现。仍是以上面的getName函数为例。

Function.prototype.bind = function () {
  const [ctx, ...args] = arguments;
  const self = this; // 这里的this即getName函数
  return function () {
    self.applay(ctx, args);
  }
}

这样,绑定this的功能咱们就实现了。而后bind实际上是能够传递参数的,bind返回的函数调用的时候也是能够再传递参数的,同时调用bind的方法多是有返回值的,因此咱们处理一下

Function.prototype.bind = function () {
  const [ctx, ...args] = arguments;
  const self = this; // 这里的this即getName函数
  return function () {
    // 这下面的arguments实际上是调用bind返回的函数,当这个函数调用时传递的参数
    // 同时返回函数执行结果
    return self.applay(ctx, args.concat(arguments));
  }
}

bind函数还有一个特性,就是bind返回的函数是能够做为构造函数的,当它做为构造函数时,它以前绑定的this会被忽略。
为了保证bind返回的函数可以继承到调用函数的原型(即getName的原型)。因此咱们须要修改bind返回函数的原型为this的原型(即getName的原型)。

咱们再来尝试一下:

Function.prototype.bind = function () {
  const [ctx, args] = arguments;
  const self = this;
  const fBond = function () {
    // 注意这里的this不是getName了,而是调用bind以后方法的this
    // const newGetName = getName.bind(this)
    // const n = new newGetName()
    // this就是n
    return self.apply(this instanceof fBond ? this : ctx, args.concat(arguments));
  }
  fBond.prototype = self.prototype;
  return fBond;
}

而后为了避免会修改原来函数的原型,即getName的原型。咱们能够经过一个中间函数来继承。

Function.prototype.bind = function () {
  if (typeof this !== "function") {
    throw new Error("Must be function to call bind");
  }

  const [ctx, args] = arguments;
  const self = this;
  var fNOP = function () {};
  const fBond = function () {
    return self.apply(this instanceof fBond ? this : ctx, args.concat(arguments));
  }
  // 这里将self的原型赋值给来fNOP,后面fBond的原型赋值为new fNOP(),这样就和self的原型断开了
  // 后面再修改fBond的原型也不会影响到self,即getName.prototype
  fNOP.prototype = self.prototype;
  fBond.prototype = new fNOP();
  return fBond;
}

这样,咱们就实现了一个比较完整的bind方法了。

前端收藏家(微信号: fedaily)

收集全网优秀前端技术资讯,与你分享,共同成长。

前端收藏家.jpg

相关文章
相关标签/搜索