手写call、apply、bind实现及详解

130 阅读2分钟

记录一下手写 call、apply、bind

三者的区别

1)三者都可以显式绑定函数的 this 指向;

2)三者第一个参数都是 this 要指向的对象,若该参数为 undefined 或 null,this 则默认指向全局 window;

3)传参不同:apply 是数组、call 是参数列表,而 bind 可以分为多次传入,实现参数的合并;

4)call、apply 是立即执行,bind 是返回绑定 this 之后的函数,如果这个新的函数作为构造函数被调用,那么 this 不再指向传入给 bind 的第一个参数,而是指向新生成的对象。

首先是 call

Function.prototype.myCall = function (context, ...args) {
  //边界判断,传空则将window赋给context
  if (context === undefined || context === null) {
    context = window;
  }
  //创建唯一的key值,防止命名重复
  let key = Symbol();
  //这个this的指向是调用call方法的构造函数,也就是将调用call的函数赋给context的key
  context[key] = this;
  // context 调用 key 来执行方法, 那么this指向的是context,也就是调用call的函数的this指向context,运行该函数,获得返回值
  let res = context[key](...args);
  //删除副作用,还原context
  // delete context[key];
  Reflect.deleteProperty(context, key); // 删除属性
  //返回 返回值
  return res;
};
let obj = {
  name: "小李",
};
function people(height, weight) {
  console.log(this); //{name: '小李', Symbol(): ƒ}
  console.log(`${this.name}、身高${height}cm、体重${weight}kg`); //小李、身高180cm、体重75kg
}
people.myCall(obj, "180", "75");

其次是 apply,apply 和 call 类似,只是接受的第二个参数为数组。

Function.prototype.myApply = function (context, args) {
  //边界判断,传空则将window赋给context
  if (context === undefined || context === null) {
    context = window;
  }
  //创建唯一的key值,防止命名重复
  let key = Symbol();
  //这个this的指向是调用call方法的构造函数,也就是将调用call的函数赋给context的key
  context[key] = this;
  // context 调用 key 来执行方法, 那么this指向的是context,也就是调用call的函数的this指向context,运行该函数,获得返回值
  let res = context[key](...args);
  //删除副作用,还原context
  delete context[key];
  //返回 返回值
  return res;
};
let obj = {
  name: "小李",
};
function people(height, weight) {
  console.log(this); //{name: '小李', Symbol(): ƒ}
  console.log(`${this.name}、身高${height}cm、体重${weight}kg`); //小李、身高180cm、体重75kg
}
people.myCall(obj, "180", "75");

最后是 bind,它可以分为多次传入,实现参数的合并,可以理解为柯里化形式传参,返回的是一个待执行的函数。

Function.prototype.myBind = function (context, ...args) {
  // bind要考虑返回的函数,作为构造函数被调用的情况
  if (context === undefined || context === null) {
    context = window;
  }
  let fn = this;
  let key = Symbol();
  const result = function (...args1) {
    if (this instanceof fn) {
      // result如果作为构造函数被调用,this指向的是new出来的对象
      // this instanceof fn,判断new出来的对象是否为fn的实例
      this[key] = fn;
      let res = this[f](...args, ...args1);
      delete this[f];
      return res;
    } else {
      // bind返回的函数作为普通函数被调用时
      context[key] = fn;
      let res = context[key](...args, ...args1);
      delete context[key];
      return res;
    }
  };
  // 如果绑定的是构造函数 那么需要继承构造函数原型属性和方法
  // 实现继承的方式: 使用Object.create
  result.prototype = Object.create(fn.prototype);
  return result;
};
let obj = {
  name: "小李",
};
function people(height, weight) {
  console.log(this); //{name: '小李', Symbol(): ƒ}
  console.log(`${this.name}、身高${height}cm、体重${weight}kg`); //小李、身高180cm、体重75kg
}
let func1 = people.myBind(obj, "180");
let func2 = func1.myBind(obj, "75");
func2();

以上都有一个问题,在构造函数中打印this,里面都包含定义的Symbol属性,原生的并不包含,这点大家可以在评论区探讨一下。