Bootstrap

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

首先,我们都知道 JavaScript 中的 this 指向分为软绑定及硬绑定。并且 this 的指向与执行上下文有关系。
在全局执行上下文中,this 指向 全局对象(window/global),在对象调用其内部方法时,this 指向调用的地方,这就称为软绑定JavaScript中this指向详解
那么如何使用硬绑定呢?这就需要使用 JavaScript 中内置的 call/aplly/bind 方法来实现了。

1.call

1.1.基本用法

  • Mdn 给出的介绍如下。翻译过来就是 :
    • call 方法会将 this 指向传入的 第一个参数,
    • call 方法可以接收多个参数
    • 调用函数执行

在这里插入图片描述

1.2.call使用的例子

软绑定的时候我们是这么使用的:

console.log(this); // this -> window
let obj = {
  name: "刘德华",
  say: function () {
    console.log("say");
    console.log(this); // this -> obj
  },
};
obj.say();

输出结果如下:

在这里插入图片描述
那么现在我们有一个新的对象,如何将 this 挂载到新对象上呢?

let o = { sing: "sing" };
// 如何将this指向o呢?
let obj = {
  name: "刘德华",
  say: function (...args) {
    console.log(args); // 此处通过解构赋值拿到所有的参数
    console.log(this); // this -> o
  },
};
// 此处调用 call,第一个参数就是要绑定的 this,后面就是要传递的参数
obj.say.call(o, o.sing, 1, 2, 3);

可以看到此时打印的 this已经不是 obj 了,而是新定义的 o:

在这里插入图片描述

1.3.自己实现一个 call 方法

思路:

  • Function.prototype 上定义一个 myCall 方法,并且 obj.say 调用自定义的 myCall 方法
  • 接收第一个参数,就是传递进来要硬绑定的新对象
  • 在新对象内部创建一个 fn 函数,将 当前 this 赋值给 fn 函数(当前thisobj.say
  • 调用新对象中的 fn 函数执行(即调用obj.say执行)
  • 完成后将新对象中的 fn 函数删除(避免占用内存)
Function.prototype.myCall = function (context, ...args) {
  console.log(this); // this -> obj.say
  console.log(args); // 拿到参数做些什么
  context.fn = this;
  context.fn();
  delete context.fn;
};
let o = { sing: "sing" };
// 如何将this指向o呢?
let obj = {
  name: "刘德华",
  say: function () {
    console.log(this); // this -> o
  },
};
obj.say.myCall(o, o.sing, 1, 2, 3);

在这里插入图片描述

这样实现有一个问题,当我们第一个参数传递的是 null 的时候是会报错的:

在这里插入图片描述
原生的 call 方法是不会报这个错误的,因此需要对参数进行兜底处理:

在这里插入图片描述
当然了,还应该将 接收的多个参数,传递给 obj.say 方法使用:

Function.prototype.myCall = function (context, ...args) {
  console.log(this); // this -> obj.say
  context = context ? context : window; // 参数兜底处理
  context.fn = this;
  context.fn(...args); // 将拿到的参数,传递给 obj.say 使用
  delete context.fn; // 完成后就删除,避免浪费内存
};
let o = { sing: "sing" };
// 如何将this指向o呢?
let obj = {
  name: "刘德华",
  say: function (...args) {
    console.log(args); // 拿到参数做些什么
    console.log(this); // this -> o
  },
};
obj.say.myCall(null, o.sing, 1, 2, 3);

2.apply

2.1.基本用法

  • 改变 this 指向
  • 传递一个参数数组
  • 调用函数执行
    在这里插入图片描述

2.2. apply使用的例子

let o = { sing: "sing" };
// 如何将this指向o呢?
let obj = {
  name: "刘德华",
  say: function (...args) {
    console.log(args);
    console.log(this); // this -> o
  },
};
obj.say.apply(o, [o.sing, 1, 2, 3]);

除了入参不同,其他和 call 没什么区别:

在这里插入图片描述

2.3. 自己实现一个apply方法

Function.prototype.myApply = function (context, args) {
  context = context ? context : window;
  context.fn = this;
  if (!args) {
    context.fn();
  } else if (Array.isArray(args)) {
    context.fn(...args);
  } else {
    return TypeError("args is not a Array");
  }
  delete context.fn;
};
let o = { sing: "sing" };
// 如何将this指向o呢?
let obj = {
  name: "刘德华",
  say: function (...args) {
    console.log(args);
    console.log(this); // this -> o
  },
};
obj.say.myApply(o, [o.sing, 1, 2, 3]);

3.bind

3.1.基本使用

  • 改变 this 指向
  • 接收多个参数
  • 返回一个新的函数
    在这里插入图片描述
let o = { sing: "sing" };
// 如何将this指向o呢?
let obj = {
  name: "刘德华",
  say: function (...args) {
    console.log(args);
    console.log(this); // this -> o
  },
};
let res = obj.say.bind(o, [o.sing, 1, 2, 3]);
res();

参数可以是任意形式,参数列表/数组都可以:

在这里插入图片描述

3.2.手动实现一个bind

Function.prototype.myBind = function (context, ...args) {
  if (typeof this !== "function") {
    throw new Error(
      "Function.prototype.bind - what is trying to be bound is not callable"
    );
  }

  var self = this;
  var args = Array.prototype.slice.call(arguments, 1);

  var fNOP = function () {};

  var fBound = function () {
    var bindArgs = Array.prototype.slice.call(arguments);
    return self.apply(
      this instanceof fNOP ? this : context,
      args.concat(bindArgs)
    );
  };

  fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();
  return fBound;
};
let o = { sing: "sing" };
// 如何将this指向o呢?
let obj = {
  name: "刘德华",
  say: function (...args) {
    console.log(args);
    console.log(this); // this -> o
  },
};
let res = obj.say.myBind(o, [o.sing, 1, 2, 3]);
res();

4.call/apply/bind的异同

  • 相同点
    • 改变 this 指向
  • 不同点:
    • call:接受的是参数列表
    • apply:接收的是一个参数数组
    • bind:返回一个新的函数,需要再次调用才执行

;