React 中的 this
一、为事件处理函数绑定上 this
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
上面例子来自 react 官网 。为了将 handleClick
函数中的 this
指向 Toogle
组件,我们需要给它手动绑定:this.handleClick = this.handleClick.bind(this)
。
这时有的小伙伴会问:如果不绑定,它的 this
就不指向 Toogle
组件吗?
确实是这样,如果我们不对它进行绑定,this
打印出来将会是 undefined
,这点将在后面介绍。
可以看到,使用 bind
绑定可以实现我们的目的,但是可能有人不喜欢这种方法。由于函数的声明在 class
中,而绑定则在 constructor
中,一方面,这样会使 constructor
变得臃肿;另一方面,函数一旦多起来,我们就需要在声明之后再回到 constructor
中完成绑定,这样显得比较麻烦,并且容易遗漏。那有没有不需要在 constructor
中手动绑定的方法呢?答案是有的。
方式一:class fields 语法
class LoggingButton extends React.Component {
// 此语法确保 `handleClick` 内的 `this` 已被绑定。
// 注意: 这是 *实验性* 语法。
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
方式二:在回调中使用箭头函数
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}
render() {
// 此语法确保 `handleClick` 内的 `this` 已被绑定。
return (
<button onClick={() => this.handleClick()}>
Click me
</button>
);
}
}
二、原因解析
有的小伙伴可能注意到了,在后面介绍的两种方式中,都使用到了箭头函数。而箭头函数的 this
是绑定词法作用域的,简单来说,箭头函数的 this
只跟它定义的地方有关,而普通函数的 this
更像是一个动态绑定的过程。由于箭头函数定义在组件中,它自然而然地就指向组件对象了。
那在定义在普通函数中的 this
默认指向什么呢?
答案是:window 对象。
很多小伙伴可能陷入了一个误区:我们将函数传给组件的 onClick
,调用它的不应该是组件对象吗,为什么会是 window 对象?
需要注意的是,我们只是将函数的引用传递给组件,但这并不意味着调用函数的就是组件对象。比如:
function A() {
this.print = function () {
console.log(this);
}
}
var a = new A();
a.print(); // 指向 A 的实例对象
var outerPrint = a.print;
outerPrint(); // 指向 window
由于普通函数的 this
指向更像是一个动态的问题,它取决于函数的调用位置。对于上面这段代码,我们称为 隐式丢失 ,不了解的小伙伴可以查一查 this
的四种绑定规则,这里不做过多介绍。
有的好奇宝宝又会问了:我明白了!这样的话,在之前的普通函数中,默认是指向 window
对象而非组件对象。但是为什么最后打印出来是 undefined
而不是 window
呢?
答案是:严格模式。
严格模式下,全局作用域函数中的 this
指向为 undefined
。而 class
则是默认使用了严格模式。
类和模块的内部,默认就是严格模式,所以不需要使用
use strict
指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。
好耶!又明白一个知识点~