上节课我们在处理点击事件时尝试了将我们的onClick函数写成类方法,但是类方法好写,在交互的过程中出现了一些问题。

我们也通过原生js解释了出现这些问题的原因,但是我们也仅仅只是理解了为何会出现这种问题,但是这个问题应该怎么解决我们却没有说。我们上节课也说了我们这节课来解决这个问题。那么好,下面我们就来讨论一下这个问题应该如何解决。

回顾

在处理上一节留下来的问题之前,我们来对上节课的一些只是要点做一个简单的回顾。

  • react类组件推荐将类组件所需要的功能模块全部封装在类中
  • 类组件中定义的方法要通过this来调用
  • 类方法赋值给变量或者作为事件监听器的回调的话,通过变量或者监听器对该方法调用则属于函数直接调用,而不是通过类的实例对象调用。
  • 类中所有方法都默认局部开启了strict模式

以上便是我们上节课对类方法相关的this指向问题所总结的知识点。接下来我们来进行今天课程的学习。

解决上节课的问题

根据上节课的学习,我们处于一个什么状态呢?就是我们也已经绑定了点击事件了,点击事件的回调函数也已经被我们改写成类方法了,但是这个回调方法经过我们当前这个方式来绑定之后,在监听器调用方法时变成了函数的直接调用。从而丢失了原本类方法中的this指向。

但是这个结果并不是我们所预期的结果。所以说我们要想办法解决上面的这个状态。我们希望的时在我们监听器调用notify方法时,该方法中的this指向的是当前组件的实例对象。那我们应该怎么处理呢?

问题解决

我们先来把这个问题解决掉,大家看看能不能看明白我这个解决方案的意图:

class Weather extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isHot: false};
    this.notify = this.notify.bind(this);
  }
  render() {return ...}
  notify() {console.log(this);}
}

我们来看一下上面这段代码,我其他的都没有改动,只是在构造器方法中添加了一行this.notify = this.notify.bind(this);对不对?但是,这样真的能解决掉这个问题吗?咱来看一下效果:

image-20211218222800687

大家看一下控制台里面,当我点击这段文字之后成功打印出了Weather组件的实例对象。证明我添加了那一行代码真的是可以解决掉我们面临的那个问题的。但是为什么?下面我们来分析一下我们添加的这一行代码。

方案分析

this.notify = this.notify.bind(this);这行代码整体上来看再简单不过了,就是一行赋值语句而已。整体上看来就是把等号右侧结果赋给this.notify,不过this.nitify是什么?就是我们定义的用于点击事件的类方法。我们在回过头来想render方法中<h2>标签中的onClick属性是什么?onClick={this.notify},但是我们在构造器中添加了一行代码,让该类组件在一初始化就使得this.notify方法变成了this.notify.bind(this),那么我们这么一等价代换,onClick={this.notify}是不是就等价于onClick={this.notify.bind(this)}

那么这就好办了。接下来我们只需要弄清楚this.notify.bind(this)这一步究竟做了什么就可以了。那么只一步究竟做了什么?一点一点来看。首先这一步可以分成三个层次thisthis.notifythis.notify.bind(this)

  1. this,这里的这个this指向的是什么?大家应该还记得,我们说过,任何类的构造器方法中的this永远都会指向这个类的实例对象。所以说这里也不例外,这里的这个this指向的就是Weather类组件的实例对象。
  2. notify这是个啥?是不是我们定义的类方法?那么我问你,this.notify是什么意思?是类方法调用吗?当然不是。这只是通过去获取到notify这个方法。然后又是一个问了千百遍的问题,notify存放在哪?存放在Weather类组件的原型对象上,供Weather类组件的实例对象调用。而this是什么?上一条里面就提到了,是Weather类组件的实例对象。那么this上面有notify吗?是不是没有?因为我到这一块,还没执行到第三个层面。那么整个这一行代码都还没有执行完,现在this.notify还没有被赋值。我们如果执行this.notify()的话,实例对象还是会直接去类的原型对象上面去找。
    或许有人要说这一段没怎么听明白。那我换一种方法来说。
    this.notify = this.notify先不加后面那段bind,那现在理解了吗?我是不是可以理解为,把等号左侧的this.notify看成一个实例对象的一个属性?我们是不是从原型对象上找到了notify方法并且把这个方法赋值给了实例对象上的一个notify属性?那么如果这么理解的话我再执行this.notify()是不是类似于我们上节课所说的吧类方法赋值给变量然后通过变量调用,就成了函数的直接调用?那么如果这个时候我们不在后面加上bind(this)的话,点击事件中点击那和之前的状况是不是还是完全一样?这还是函数直接调用,this指向依然还是会丢失。所以说接下来进入到第三个层面
  3. this.notify.bind(this)这一步是什么意思呢?我们上一个层面是不是已经说了,如果不加bind(this)那么和不加这行代码没什么区别,依然还是函数直接调用,this指向丢失的问题就解决不了。但是bind(this)究竟做了什么呢?bind方法其实在这做了两件事,第一帮你生成一个新的函数,第二帮你修改函数中的this指向。至于修改了什么,那要看你传入了什么。我们传入了this,依然还是那个老问题,this指向是什么?在构造器里面,this指向类的实例对象,所以说这里bind(this)通过this.notify生成了一个新函数,并且将生成的新函数中的this指向改成了当前类的构造器方法中的this

那么经过上面三个层面的分析。我想大家应该能明白这一行代码是什么意思了吧?来总结一下:

方案原理总结

在构造器方法中通过this去类组件的原型对象上找到notify方法,然后将该方法通过bind(this)来生成一个新函数,并将这个新函数中this的指向修改成了<Weather>类组件的构造器方法中的this,最后将这个生成的新函数复制给<Weather>类组件的实例对象上的notify属性。

其实这一步操作相当于是在<Weather>类组件的实力对象自身加了一个类方法,而且这个类方法也叫notify,我们来验证一下是不是:

image-20211218232451889

我们从图片上可以很直观得看出在这个实力对象自身上面存放了一个叫做notify的方法。但是你说我们这时候再来点击这段文字的时候,我们onClick监听器调用的this.notify到底是自身的notify还是原型链上的notify呢?这答案显而易见,肯定是自身的notify,我们再回想一下类方法和类属性的查找顺序,当我们调用属性或者方法的时候先从自身查找,自身没有的时候再去通过原型链上原型对象上查找,所以现在我们自身已经有了notify了,就直接调用自身的notify方法了。

至此,我们就解决了我们类组件回调方法中的this指向问题。

总结

  • bind(this)做了两件事
  • 生成新函数
  • 修改新函数中的this指向
  • 实例对象调用属性和方法会先从自身查找,自身没有才会顺着原型链去查找原型对象
  • this.notify = this.notify.bind(this)是将原型对象上的notify方法生成新函数并修改this指向之后存放在实例对象自身

Copyright statement:The articles of this site are all original if there is no special explanation, indicate the source please when you reprint.

Link of this article:https://work.lynchow.com/article/solve_this_problem/