又是this惹的祸?

前言

知其然而不知其所以然是编程当中我们经常遇到的,最近在学习React中也遇到这么一个问题,就是为啥每次绑定一个事件到DOM元素上都要使用个.bind(this),当时因为对React中很多概念不太熟悉,于是就把它落下没有深究,今天我对这一问题有所深究后,写一个笔记,帮助到他人理解这个bind到底是什么意思,也巩固自己对知识的理解。

一个简单的Bug

我们先从报错来探究问题的所在,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'Vince'
};
}
sayName(){
console.log(this.state.name)
}
render() {
return (
<div>
<button onClick={this.sayName} >sayName</button>
</div>
);
}
}

当我点击button的时候,调试台直接报错:

image

我们来看看错误:第一个醒目的点就是property上没有state这个值,但是明明sayName在class里面呀,sayName里面的this不就是class里面的this吗?

别急,我们一步步来,我们先将sayName改为下面这样:

1
2
3
sayName(){
console.log(this)
}

我们再点击button,发现这次输出的undefined。这就奇了怪呀,原来this并不是绑定在class中,原来.bind(this)就是为了将sayName中的this绑定到class中,于是我们加上.bind(this):

1
<button onClick={this.sayName.bind(this)} >sayName</button>

当我们再点击button,这下,就将this绑定到了class上,好了问题解决了,本文完。

image

开个玩笑啦,哈哈哈,事情并没有那么简单,我们本文要探究的是,为什么要bind(this),
也就是探究,为什么this不会按照我们相信地自动绑定到class中呢?

为什么不自动绑定?

我们来看官方文档是怎么说的:

“方法遵循正式的ES6 class的语义,意味着它们不会自动绑定this到实例上。建议在构造函数中绑定事件处理器,这样对于所有实例它们只需绑定一次”

这里明确说明,不会自动绑定this,那么,这个this到底绑定到了哪里去呢?

为了说明白这个问题,我们这里就要牵涉到一个很难理解的this,其实也不难理解,只要记住:函数中使用的 this,绑定的是运行时的对象,而不是定义时的对象。

大家一起跟我读:“函数中使用的 this,绑定的是运行时的对象,而不是定义时的对象”,
这个时候,我们再来理解,sayName是定义在App类上,那么是在哪运行的呢?

答案: 在Button 组件里运行,这里的this实则是指buttob组件上的this,所以和类App一点关系都没有,这个时候就可以通过bind改变函数运行时候this的指向。

如何优雅bind

我们常常会看到他人的代码,其中有两个派别,第一个是在render函数中.bind(this),第二个是在constructor 中.bind(this),那么两者之间,有啥区别呢?

  • render函数中.bind(this):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'Vince'
};
}
sayName(){
console.log(this.state.name)
}
render() {
return (
<div>
<button onClick={this.sayName.bind(this)} >sayName</button>
</div>
);
}
}

这里我们要明白一个点,render函数在this.state变化后,会自动再次render,也就意味着,以这种方式bind,只要改变了state,都会执行一次bind,也就是在浏览器中做了一次运算,这是万万不可取的,只要有一点性能能补上,我们都不能放过。

  • constructor 中.bind(this):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'Vince'
};
this.sayName = this.sayName.bind(this)
}
sayName(){
console.log(this.state.name)
}
render() {
return (
<div>
<button onClick={this.sayName} >sayName</button>
</div>
);
}
}

我们知道React中的生命周期是constructor只执行一次,那么这个时候.bind(this),只会执行一次,并且在组件卸载之前都有效,这就符合我们的性能优化了。

结论: 通过原理上的对比,我们强烈推荐后者。

总结

这虽然是个简单的问题,而且好像影响也不是很大,大部分初学者想着“不出错,能运行”就ok的思想,我觉得这是不对的,写代码最怕的就是细节,程序员之间最怕比较的就是细节。好啦,这里到此为止,希望能给大家一点启发和帮助。

Thought always spent in cigarettes, total love in alcohol broken. :)