Browse Source

note

pull/43/head
jingxun 2 years ago
parent
commit
ea96a5140c
  1. 65
      day08/note/lesson016_solve_problem_this.md

65
day08/note/lesson016_solve_problem_this.md

@ -11,4 +11,67 @@
- `react`类组件推荐将类组件所需要的功能模块全部封装在类中
- 类组件中定义的方法要通过`this`来调用
- 类方法赋值给变量或者作为事件监听器的回调的话,通过变量或者监听器对该方法调用则属于函数直接调用,而不是通过类的实例对象调用。
- 类中所有方法都默认局部开启了`strict`模式
- 类中所有方法都默认局部开启了`strict`模式
以上便是我们上节课对类方法相关的`this`指向问题所总结的知识点。接下来我们来进行今天课程的学习。
## 解决上节课的问题
根据上节课的学习,我们处于一个什么状态呢?就是我们也已经绑定了点击事件了,点击事件的回调函数也已经被我们改写成类方法了,但是这个回调方法经过我们当前这个方式来绑定之后,在监听器调用方法时变成了函数的直接调用。从而丢失了原本类方法中的`this`指向。
但是这个结果并不是我们所预期的结果。所以说我们要想办法解决上面的这个状态。我们希望的时在我们监听器调用`notify`方法时,该方法中的`this`指向的是当前组件的实例对象。那我们应该怎么处理呢?
### 问题解决
我们先来把这个问题解决掉,大家看看能不能看明白我这个解决方案的意图:
```jsx
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](https://file.lynchow.com/2021-12-18-142803.png)
大家看一下控制台里面,当我点击这段文字之后成功打印出了`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)`这一步究竟做了什么就可以了。那么只一步究竟做了什么?一点一点来看。首先这一步可以分成三个层次`this`、`this.notify`、`this.notify.bind(this)`。
1. `this`,这里的这个`this`指向的是什么?大家应该还记得,我们说过,任何类的构造器方法中的`this`永远都会指向这个类的实例对象。所以说这里也不例外,这里的这个`this`指向的就是`Weather`类组件的实例对象。
1. `notify`这是个啥?是不是我们定义的类方法?那么我问你,`this.notify`是什么意思?是类方法调用吗?当然不是。这只是通过去获取到`notify`这个方法。然后又是一个问了千百遍的问题,`notify`存放在哪?存放在`Weather`类组件的原型对象上,供`Weather`类组件的实例对象调用。而`this`是什么?上一条里面就提到了,是`Weather`类组件的实例对象。那么`this`上面有`notify`吗?是不是没有?因为我到这一块,还没执行到第三个层面。那么整个这一行代码都还没有执行完,现在`this.notify`还没有被赋值。我们如果执行`this.notify()`的话,实例对象还是会直接去类的原型对象上面去找。<br/>或许有人要说这一段没怎么听明白。那我换一种方法来说。<br/>`this.notify = this.notify`先不加后面那段`bind`,那现在理解了吗?我是不是可以理解为,把等号左侧的`this.notify`看成一个实例对象的一个属性?我们是不是从原型对象上找到了`notify`方法并且把这个方法赋值给了实例对象上的一个`notify`属性?那么如果这么理解的话我再执行`this.notify()`是不是类似于我们上节课所说的吧类方法赋值给变量然后通过变量调用,就成了函数的直接调用?那么如果这个时候我们不在后面加上`bind(this)`的话,点击事件中点击那和之前的状况是不是还是完全一样?这还是函数直接调用,`this`指向依然还是会丢失。所以说接下来进入到第三个层面
1. `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](https://file.lynchow.com/2021-12-18-152454.png)
我们从图片上可以很直观得看出在这个实力对象自身上面存放了一个叫做`notify`的方法。但是你说我们这时候再来点击这段文字的时候,我们`onClick`监听器调用的`this.notify`到底是自身的`notify`还是原型链上的`notify`呢?这答案显而易见,肯定是自身的`notify`,我们再回想一下类方法和类属性的查找顺序,当我们调用属性或者方法的时候先从自身查找,自身没有的时候再去通过原型链上原型对象上查找,所以现在我们自身已经有了`notify`了,就直接调用自身的`notify`方法了。
至此,我们就解决了我们类组件回调方法中的`this`指向问题。
## 总结
- `bind(this)`做了两件事
- 生成新函数
- 修改新函数中的`this`指向
- 实例对象调用属性和方法会先从自身查找,自身没有才会顺着原型链去查找原型对象
- `this.notify = this.notify.bind(this)`是将原型对象上的`notify`方法生成新函数并修改`this`指向之后存放在实例对象自身

Loading…
Cancel
Save