基本原则
- class的本质是function。它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法
- 类中定义的方法,都放在了类的原型上,供实例对象调用
- 类中可以直接写赋值语句,写一个a = 1,就是给实例对象添加一个属性,名为a,值为1
- 构造器中的this是 类的实例对象
class Student {
study () {
console.log(this)
}
}
- 通过Student实例对象调用study方法时,study中的this就是Student实例对象
用一个React的demo来分析this的指向问题
class Weather extends React.Component{
constructor (props) {
super(props)
this.state = { isHot: false }
}
render () {
const { isHot } = this.state
return <h1 onClick={ this.changeWeather }>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
}
changeWeather () {
const isHot = this.state.isHot
this.setState({ isHot:!isHot })
console.log(this);
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
实现效果:点击h1标签中的内容,能实现“凉爽”与“炎热”的切换
这种写法是错误的,点击h1内容时,会报错:Uncaught TypeError: Cannot read properties of undefined (reading ‘state’)
这是因为
<h1 onClick={ this.changeWeather }>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用,里面的this应该是全局window。而类中的方法已经在局部开启了严格模式,所以里面的this就会指向undefined,undefined.state就会报错
就是类似于一个这样的demo
const p1 = new Person('tom',18)
p1.study() //通过实例调用study方法
const x = p1.study
x() // 直接调用
解决方案一:bind
在构造器里写一句
this.changeWeather = this.changeWeather.bind(this)
分析这句话,它是一个赋值语句,我们就从右边开始分析:this.changeWeather.bind(this)。这句话写在构造器里,构造器里的this是实例对象,我们在class里实现的changeWeather方法放在实例对象的原型上,this顺着原型链向上查找就找到了changeWeather方法(this.changeWeather的由来)
bind能做两件事:
- 帮你生成一个新的函数
- 帮你改了函数里的this
由于构造器里的this就是Weather实例对象
this.changeWeather.bind(this) 它执行完后,你手里就有了一个新的函数,这个函数里的this已经成功变成了Weather实例对象
然后this.changeWeather = this.changeWeather.bind(this)
你把这个函数放到了实例对象的自身,还给这个函数起了一个名字,叫做changeWeather。这时候你的实例对象自身就多了一个方法,叫做changeWeather
也就是说:拿着原型上的changeWeather,生成新的、修改过函数内部this指向的changeWeather,并挂在自身。
解决方案二:赋值语句+箭头函数
class Weather extends React.Component{
state = { isHot: false }
render () {
const { isHot } = this.state
return <h1 onClick={ this.changeWeather }>今天天气很{isHot ? '炎热' : '凉爽'}</h1>
}
changeWeather = () => {
const isHot = this.state.isHot
this.setState({ isHot: !isHot })
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
- 把类中的方法用“赋值语句”来写,这个方法就挂在实例对象自身了,而不是挂在其原型上
- 使用箭头函数,函数体中的this就是其外部作用域的this。那里的this就是组件实例对象
- 构造器也可以省略不写了,实际上完全没必要写
箭头函数补充
菜鸟教程里有一句话说得特别好:箭头函数体中的this对象,是定义函数时的对象,而不是使用函数时的对象
function fn(){
setTimeout(()=>{
// 定义时,this 绑定的是 fn 中的 this 对象
console.log(this.a);
},0)
}
var a = 20;
// fn 的 this 对象为 {a: 18}
fn.call({a: 18}); // 18