JavaScrip 中的 this, bind, call & apply 简述

JavaScrip 中的 this, bind, call & apply 简述

this 是一个比较特殊的东西,基本上可以理解成 this 的指向是就近调用的指向,因此 this 在 JS 中也是一个比较令人困惑的知识点。

之前绕过 this 的方法基本上采用 arrow function,因为 arrow function 不包含对 this, arguments, 或者 super 的绑定,因此使用 arrow function 时,this 的指向更容易判断一些,不过最近感觉也是时候细究一下 this, call, applybind 了。

this 的指向问题

上面说了,this 的指向采用的是就近调用原则,以下面的代码为例:

const person = {
    
    
  name: 'John',
  greet() {
    
    
    console.log(this);
    console.log('Hello ' + this.name);
  },
};

正常情况下,greet 中的 this 指向应该指代的是 person,在下面这种调用的情况下是正确的:

person.greet();

在这里插入图片描述

这个情况下,greet 是通过 person 这个对象去调用的,因此 greet 绑定的环境是 person 对象本身。

不过换一种方式调用,就会有不同的结果:

const {
    
     greet } = person;
greet();

这时候的 greet 指向就变成了 global,或者在严格模式下变为 undefined:

在这里插入图片描述

在这里插入图片描述

这是因为 greet 被解构出来了,在这个时候调用它的是 global 对象下,换言之,这个时候的 this 就会自动绑定成 global 对象。

再换一种调用的方法:

const user = {
    
    
  name: 'user',
};

user.greet = person.greet;

user.greet();

这个时候的 greet 因为是被 user 调用的,因此绑定的对象就是 user:

在这里插入图片描述

大多数情况下 this 的问题不是很大,不过有的时候用到 callback 时就会是一个比较麻烦的事情,如下面模拟一个挂载 callback,并且实现延后执行的情况:

const example = {
    
    
  addEventListener(cb) {
    
    
    this.cb = cb;
  },
  click() {
    
    
    this.cb();
  },
};

example.addEventListener(person.greet);
example.click();

这时候调用 greet 中的指向就变成了 example

在这里插入图片描述

这种方法也是很多时候 behind the scene 的实现,因此 this 的指向是相当不清晰的。

在一些情况下,这也就是 bind, callapply 可以帮忙的地方。

bind

bind 会创建一个新的函数,同时 bind 会将 this 绑定成第一个传过去的参数。

遥想当初使用 React 的 class based component 时,在没有使用 arrow function 的时候,都是使用 bind 去执行的:

class ExplainBindingsComponent extends Component {
    
    
  constructor() {
    
    
    super();

    this.onClickMe = this.onClickMe.bind(this);
  }

  onClickMe() {
    
    
    console.log(this);
  }
}

这就是为了将方法中的 this 绑定到当前类中,使其不随从就近原则。这里的第一个参数就是想要绑定的对象,在无所谓第绑定的对象时可以用 null,使用 this 指代的是当前对象,也可以明确的使用一个已经实例化的对象。

上面的几个案例使用 bind 绑定的改写方法为:

// 'use strict';

const person = {
    
    
  name: 'John',
  greet() {
    
    
    console.log(this);
    console.log('Hello ' + this.name);
  },
};

const {
    
     greet } = person;
greet();
greet.bind(person)();

const user = {
    
    
  name: 'user',
};

user.greet = person.greet.bind(person);

user.greet();

const example = {
    
    
  addEventListener(cb) {
    
    
    this.cb = cb;
  },
  click() {
    
    
    this.cb();
  },
};

example.addEventListener(person.greet.bind(person));
example.click();

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这样都可以通过 bindthis 绑定到固定的对象上,使其不受就近原则的影响。另外,bind 也可以接受其他的参数,并将其传到调用的函数中,如:

const person = {
    
    
  name: 'John',
  greet(from) {
    
    
    console.log(this);
    console.log('Hello ' + this.name + ' from ' + from);
  },
};

const {
    
     greet } = person;
// greet();
greet.bind(person, 'Sam')();

在一些情况下,使用 bind 也可以简化代码,如下面的代码可以实现一段基础的加减乘除的操作:

const math = {
    
    
  accumulated: 0,
  add(num) {
    
    
    const oldNum = this.accumulated;
    this.accumulated += num;
    console.log(`${
      
      oldNum} + ${
      
      num} = ${
      
      this.accumulated}`);
  },
  substract(num) {
    
    
    const oldNum = this.accumulated;
    this.accumulated -= num;
    console.log(`${
      
      oldNum} - ${
      
      num} = ${
      
      this.accumulated}`);
  },
  multiply(num) {
    
    
    const oldNum = this.accumulated;
    this.accumulated *= num;
    console.log(`${
      
      oldNum} * ${
      
      num} = ${
      
      this.accumulated}`);
  },
  divide(num) {
    
    
    const oldNum = this.accumulated;
    this.accumulated /= num;
    console.log(`${
      
      oldNum} / ${
      
      num} = ${
      
      this.accumulated}`);
  },
};

math.add(10);
math.substract(5);
math.multiply(2);
math.divide(5);

在这里插入图片描述

这种情况下,可以使用 bind 去简化操作:

const math = {
    
    
  accumulated: 0,
  calculation(operation, num) {
    
    
    let oldNum = this.accumulated;
    if (operation === '+') {
    
    
      this.accumulated += num;
    } else if (operation === '-') {
    
    
      this.accumulated -= num;
    } else if (operation === '*') {
    
    
      this.accumulated *= num;
    } else if (operation === '/') {
    
    
      this.accumulated /= num;
    }
    console.log(`${
      
      oldNum} ${
      
      operation} ${
      
      num} = ${
      
      this.accumulated}`);
  },
};

const {
    
     calculation } = math;
calculation.bind(math, '+', 10)();
calculation.bind(math, '-', 5)();
calculation.bind(math, '*', 2)();
calculation.bind(math, '/', 5)();

最后的运行结果也是一致的:

在这里插入图片描述

不是说一定要用 bind 去执行,也可以用其他的方式使得代码更加清晰可读,并提高复用性

call & apply

callapply 两个不会创建一个新的函数,但是它们的用法和 bind 相似,第一个参数都是 this 的指向,只不过 call 可以传递无限多的参数,而 apply 会接受一个 array like 的结构作为第二个参数:

call(thisArg, arg1, /* …, */ argN);

apply(thisArg, argsArray);

根据 mdn 所说,通常情况下,在不涉及到 constructed 的情况下,bindcall 是可以被视作效果相同的:

You can generally see const boundFn = fn.bind(thisArg, arg1, arg2) as being equivalent to const boundFn = (...restArgs) => fn.call(thisArg, arg1, arg2, ...restArgs) for the effect when it’s called (but not when boundFn is constructed).

reference

猜你喜欢

转载自blog.csdn.net/weixin_42938619/article/details/130738314