一文让你彻底理解call、apply、bind(小白看完也能手写一个)

121 阅读5分钟

想要彻底理解,最好的方式就是手写,而手写call、apply、bind也是非常高频的面试题


定义

call 和 apply 只有传参的区别,call、apply 和 bind 的区别是 bind 不会立即执行函数,而 call、apply 会立即执行 因此我们先实现 call,再实现 apply,最后用 call 或者 apply 就能实现 bind

call 实现过程

首先先用这个例子复习一下 call 的功能

const student = {
  name: "lwp",
  sayHello: function (age) {
    console.log(`my name is ${this.name},I am ${age} old`);
  },
};
const lwpSayHello = student.sayHello;
student.sayHello()//my name is lwp,I am 29 old: 谁调用,this指向谁
lwpSayHello(29); //my name is undefined,I am 29 old: 普通函数调用,this指向全局
lwpSayHello.call(student, 29); //my name is lwp,I am 29 old: 通过this绑定,绑定谁,this就指向谁

首先四行核心代码快速实现功能

Function.prototype.myCall = function (context, ...args) {
  context.fn = this;
  const result = context.fn(...args);
  delete context.fn;
  return result;
};
lwpSayHello.myCall(student, 29); //my name is lwp,I am 29 old

比较不好理解的是这句context.fn = this;,而这句话是实现 call 的核心 为了更好理解 context.fn,我们这样写

Function.prototype.myCall = function (context, ...args) {
  var fn = this;
  const result = fn(...args);
  return result;
};
lwpSayHello.myCall(student, 29); //my name is undefined,I am 29 old

然后你会发现 this 指向是错误的

现在我开始一步一步解析代码,请大家集中精神跟紧我的脚步,我要开车了

Function.prototype.myCall = function (context, ...args) {
  context.fn = this;
  const result = context.fn(...args);
  delete context.fn;
  return result;
};
var student = {
  name: "lwp",
  sayHello: function (age) {
    console.log(`my name is ${this.name},I am ${age} old`);
  },
};
var lwpSayHello = student.sayHello;
lwpSayHello.call(student, 29)
//想要绑定this,在这个例子我们需要做的就是让sayHello函数里面的this指向student
context.fn = this;
//那么,这句代码我们可以理解为两个步骤
//第一步 把student代入到context
context=student={
    name: "lwp",
    sayHello: function (age) {
        console.log(`my name is ${this.name},I am ${age} old`);
  },
}
//第二步,把this代入,谁调用this,this指向谁,lwpSayHello.call,所以this指向lwpSayHello
context={
    name: "lwp",
    sayHello: function (age) {
        console.log(`my name is ${this.name},I am ${age} old`);
    }
    fn:lwpSayHello
}
//第三步,把lwpSayHello拷贝进去,也就是student.sayHello,也就是
function (age) {
    console.log(`my name is ${this.name},I am ${age} old`);
 }
//那么最终
context={
    name: "lwp",
    sayHello: function (age) {
        console.log(`my name is ${this.name},I am ${age} old`);
    }
    fn:function (age) {
        console.log(`my name is ${this.name},I am ${age} old`);
  }
}
//最后我们再理解myCall里面的这句话
const result = context.fn(...args)
//因此this.name就能正确变成lwp
//总结一下,借助context,新增fn函数,那么context.fn函数里面的this也就是context,目的就是为了改变this指向,最后把多余的fn删掉就行

还可以这样理解

call2.png

想要让 this 指向 student,那么在 student 去增加一个属性 fn,把函数赋值给 fn,调用完成之后再删除,那么在调用函数的时候,调用函数的对象就是 student,那么在函数内部访问的 this 也就是 student

当时我也理解不了为什么要用 context.fn,看了这个视频才恍然大悟的,所以如果看了我的解释还看不懂,你可以看视频:www.bilibili.com/video/BV15s…

也可以按照我的思路,自己写一下代码,用例子执行一下去理解

这里面还有很多问题,比如说 fn 会不会同名,然后覆盖,那么我们这里可以借助 symbol的唯一性

Function.prototype.myCall = function (context, ...args) {
  const key = Symbol("changeThis");
  context[key] = this;
  const result = context[key](...args);
  delete context.fn;
  return result;
};

但是仍然有问题,打印this的时候会发现多了一个symbol属性

截屏2023-08-10 19.48.28.png 最后通过Object.defineProperty,然后考虑一下边界问题得到满分代码

Function.prototype.myCall = function (context, ...args) {
  var context = context || globalThis; //globalThisl可能是浏览器的window也可能是node的global
  const key = Symbol("changeThis");
  Object.defineProperty(context, key, {
    enumerable: false,
    value: this,
  });
  const result = context[key](...args);
  return result;
};

到此为止 call 就实现了,接下来实现 apply 和 bind 也会变得非常简单

apply实现过程

apply和call也就是一个传参问题,无需解释

Function.prototype.myApply = function (context, args) {
  var context = context || globalThis; //globalThisl可能是浏览器的window也可能是node的global
  const key = Symbol("changeThis");
  Object.defineProperty(context, key, {
    enumerable: false,
    value: this,
  });
  const result = context[key](...args);
  return result;
};

bind实现过程

bind只是在apply基础上加了一层函数

Function.prototype.myBind = function (context, ...args) {
  const context = context || globalThis; //globalThisl可能是浏览器的window也可能是node的global
  const that = this;
  return function (...innerArgs) {
    return that.apply(context, [...args, innerArgs]);
  };
};

这样调用的时候才会有结果

var student = {
  name: "lwp",
  sayHello: function (age, gender) {
    console.log(
      `my name is ${this.name},I am ${age} old,my gender is ${gender}`
    );
  },
};
var lwpSayHello = student.sayHello;
lwpSayHello(); //my name is undefined,I am undefined old
const say = lwpSayHello.myBind(student, 29);
say("男"); //my name is lwp,my gender is 男

但是如果遇到 new,就会出现问题 因此最终代码

Function.prototype.myBind = function (context, ...args) {
  const _context = context || globalThis; //globalThisl可能是浏览器的window也可能是node的global
  const that = this;
  return function F(...innerArgs) {
    return that.apply(this instanceof F ? this : _context, [
      ...args,
      innerArgs,
    ]);
  };
};

完整代码整理

call

Function.prototype.myCall = function (context, ...args) {
  var context = context || globalThis; //globalThisl可能是浏览器的window也可能是node的global
  const key = Symbol("changeThis");
  Object.defineProperty(context, key, {
    enumerable: false,
    value: this,
  });
  const result = context[key](...args);
  return result;
};

apply

Function.prototype.myApply = function (context, args) {
  var context = context || globalThis; //globalThisl可能是浏览器的window也可能是node的global
  const key = Symbol("changeThis");
  Object.defineProperty(context, key, {
    enumerable: false,
    value: this,
  });
  const result = context[key](...args);
  return result;
};

bind

Function.prototype.myBind = function (context, ...args) {
  const _context = context || globalThis; //globalThisl可能是浏览器的window也可能是node的global
  const that = this;
  return function F(...innerArgs) {
    return that.apply(this instanceof F ? this : _context, [
      ...args,
      innerArgs,
    ]);
  };
};

打波广告

更详细的过程还有代码可以看这里

截屏2023-08-10 19.53.58.png

很多面经找起来很麻烦,也找不全,然后还得自己找答案,有些面试宝典虽然完整,但是看不懂,因此干脆自己进行整理,整理最全的前端面试题以及最容易理解的答案,附上参考链接或者视频链接,再加上自己调试的代码,我不相信这样还看不懂。 地址

也可以一起讨论:wx:lan_weipeng备注前端面试群