认识call、apply、bind

161 阅读4分钟

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战

call

MDN上说:call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数;
解释一下,call是一个函数,一个可以指定this值的函数,一个可以接受一个或者多个参数调用放入函数。

证明一下:

function Person(name, price) {
  this.name = name;
  this.price = price;
}

function Man(name, price) {
  Person.call(this, name, price);
}
const obj = new Man('张三', '男');
console.log(obj);
console.log('obj', obj);// { name: '张三', price: '男' }

上述代码和结果证明确实,Man函数存在了自己没有的属性和属性值,为什么会这样?call如何做到的?

  • 首先call函数回接受最少1个参数 context + 一个可能的数组参数 args
  • context需要判断是否为空,如果为空,将 context 指向全局对象,浏览器下 windos,node 环境 global
  • 此时的 context 已经是全局对象或者是传入的 this ,总之一定不为空且是一个对象
  • 将 call 函数中的 this 存放在 context 中,为了存放this的属性名与已有的属性名冲突 context 属性名最好使用 Symbol
  • call 函数中的 this 此时被谁调用就会指向谁
  • 将 context 中的 this 执行,并且将参数 args 传入 this
  • 返回 this 执行结果;此时 调用call函数被执行,执行时this被指向到使用call的this;

上述逻辑比较绕,请看下面代码,逐行注释为你解惑

Function.prototype.myCall = function (context, ...args) {
    // context 表示Man的this,如果为空,将 context 指向全局对象,浏览器下windos,node环境global
  if (typeof context === 'undefined' || context === null) {
    context = window;
  }
  // 此处使用Symbol,防止context当前命名的属性值key与context已有的属性名冲突
  let key = Symbol();
  // 在man对象上挂载this,这里的this应为被Person调用,所以此处的this指向的是Person
 // context[key] = this;
// 此处用不可枚举属性
 Object.defineProperty(context, key, {
    value: this,
    //是否为枚举属性
    enumerable: false,
  })
  // 执行 Person 函数,因为此处是context调用,context又是Man的this;
  // 所以 Person 函数执行的时候,Person中this就指向了context,指向了Man
  let fn = context[key](...args);
    // 删除对象上的函数
  delete context[key];
  // Person函数的返回值。若没有返回值,则返回 `undefined`
  return fn;
};

function Person(name, price) {
  this.name = name;
  this.price = price;
}

function Man(name, price) {
    // 将Person中属性挂载到Man函数
  Person.myCall(this, name, price);
  this.category = 'food';
}
const obj = new Man('张三', '男');
console.log('obj', obj);

apply

apply和call逻辑类似,不同点在于call的参数是一个一个的,apply的参数是数组形式的;所以二者除了对参数的获取形式不同,其他逻辑基本一致

Function.prototype.myApply = function (context, args) {
    // 此处args是数组,已经是数组,不需要使用扩展符展开;此处可以增加args是否为数组的判断

    // context 表示Man的this,如果为空,将 context 指向全局对象,浏览器下windos,node环境global
  if (typeof context === 'undefined' || context === null) {
    context = window;
  }
  // 此处使用Symbol,防止context为window是,命名的key与window已有的属性名冲突
  let key = Symbol();
  // 在man对象上挂载this,这里的this应为被Person调用,所以此处的this指向的是Person
  context[key] = this;
  // 执行 Person 函数,因为此处是context调用,context又是Man的this;
  // 所以 Person 函数执行的时候,Person中this就指向了context,指向了Man
  let fn = context[key](...args);
    // 删除对象上的函数
  delete context[key];
  // Person函数的返回值。若没有返回值,则返回 `undefined`
  return fn;
};

bind

bind与call、apply又不同;bind是绑定,this绑定之后返回一个函数,在执行函数的时候this才会改变

Function.prototype.myBind = function (context) {
    // 判空
  if (typeof context === 'undefined' || context === null) {
    context = window;
  }
  // 此时 context 是 Man 的this
  
  self = this;
   // 此时 self 是 Person 的this
  return function (...args) {
     // 此处可以理解为 Person 使用 apply 修改 Man的 this 值
    return self.apply(context, args);
  };
};

function Person(name, price) {
  this.name = name;
  this.price = price;
}

function Man(name, price) {
  Person.call(this, name, price);
  console.log(this);
}

function Man2(name, price) {
  Person.myBind(this)(name, price);
}
const obj2 = new Man2('张三2', '男2');
console.log(obj2.name);

结语

虽然是逐行理解,但是技术水平有限,不一定能理解透彻。如有问题希望读者不吝赐教。感激之至