在深入浅出 React(一):React 的设计哲学 - 简单之美一文中,开头提到了react取消函数自动绑定的改变,作者引用这个例子是想说明react的设计哲学,但这却引起了我对绑定事件的注意。在此之前,我对bind()这种方法都没怎么听闻,而我也想因此来看看绑定this的这背后的奥秘。
react的“反向更新”?
先引用原文如下
上个月 React 发布了最新的 0.13 版,并提供了对 ES6 的支持。在新版本中,一个小小的改变是 React 取消了函数的自动绑定,也就是说,以前可以这样去绑定一个事件:
<button onClick={this.handleSubmit}>Submit</button>而在以 ES6 语法定义的组件中,必须写为:
<button onClick={this.handleSubmit.bind(this)}>Submit</button>了解前端开发和 JavaScript 的同学都知道,做事件绑定时我们需要通过 bind(或类似函数)来实现一个闭包以让事件处理函数自带上下文信息,这是由 JavaScript 语言特性决定的。而在 0.13 版本之前,React 会自动在初始化时对组件的每一个方法做一次这样的绑定,类似于
this.func = this.func.bind(this),这样在 JSX 的事件绑定中就可以直接写为onClick={this.handleSubmit}。
这个很有意思啊, Facebook 认为自动绑定这破坏了JavaScript 的语言习惯,然后react“开倒车”,取消了原先的优化代码。这个更新的好与坏呢,我也不太知道,但我们可以从这个事件绑定出发,先来分析一下this的指向。
this的指向
一般来说,在经历多次回调后,this的指向会发生一些变化。
es6中this的指向
var name = 'Global'
const fish = {
name: 'Fish',
greet: function() {
console.log('Hello, I am ', this.name);
}
};
fish.greet(); // Hello, I am Fish
const greetCopy = fish.greet;
greetCopy(); // Chrome: Hello, I am Global
// Node.js: Hello, I am undefined
在上述代码中,我们使用了.来显式调用greet函数,那么显示调用能找到函数的调用者,则this指向该调用者;但是由于greetCopy这个函数不是一个显示调用,找不到它的一个调用者,因此它的this指向全局对象(浏览器中为windows)。
请注意:当我们在使用严格模式时(use strict),greetCopy指向为undefined。因此我们在使用 onClick={this.handleClick}来绑定事件监听函数的时候,handleClick 函数实际上会作为回调函数,传入 addEventListener() 。这就是为什么在 React 的组件中添加事件处理函数为什么会得到 undefnied 而不是全局对象或者别的什么东西。
箭头函数中this的指向
不得不提的还有箭头函数,简言之
this永远绑定了定义箭头函数所在的那个对象。(它的this指向就要在函数一开始定义的时候就确定好了,与谁调用它没关系)箭头函数中的this都是外层的this,会向外作用域中,一层层查找this,直到有 this 的定义
vue中this的指向
还有关于this的是vue中,在Vue所有的生命周期钩子方法(如created,mounted, updated以及destroyed)里使用this,this指向调用它的Vue实例。在普通函数中可以适用上述。
由于所有的生命周期钩子自动绑定 this 上下文到实例中,这意味着你不能使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos())。这是因为箭头函数绑定了父上下文。(建议vue生命周期中不用要箭头函数,踩过好几次坑😥)
bind()函数
接下来看看js中用于解决this指向问题的一大方法:bind()
根据mdn原文
bind() 函数会创建一个新的绑定函数(bound function,BF)。绑定函数是一个 exotic function object(怪异函数对象,ECMAScript 2015 中的术语),它包装了原函数对象。调用绑定函数通常会导致执行包装函数。 绑定函数具有以下内部属性:
- [[BoundTargetFunction]] - 包装的函数对象
- [[BoundThis]] - 在调用包装函数时始终作为 this 值传递的值。
- [[BoundArguments]] - 列表,在对包装函数做任何调用都会优先用列表元素填充参数列表。
- [[Call]] - 执行与此对象关联的代码。通过函数调用表达式调用。内部方法的参数是一个this值和一个包含通过调用表达式传递给函数的参数的列表。
当调用绑定函数时,它调用 [[BoundTargetFunction]] 上的内部方法 [[Call]] ,就像这样 Call(boundThis, args) 。其中,boundThis 是 [[BoundThis]] ,args 是 [[BoundArguments]] 加上通过函数调用传入的参数列表。
那么可见bind()函数一大主要功能是解决了this在回调中所出现的问题,通过构造一个原函数对象,以解决多次调用中this指向不明的问题。
如何解决事件的绑定问题?
好,说了那么多,还是回到开头这个react的更新上来,在现版本中,我们该如何绑定一个事件呢
第一种:onClick = {this.yourEvent} 需要在构造函数中绑定this或onClick = {this.yourEvent.bind(this)},直接调用
import React from 'react'
function BindThisDemo(){
this.bindClick = this.bindClick.bind(this);
bindClick(){
alert('我是在构造函数中绑定this')
}
bindClick2(){
alert('我是在绑定事件中绑定this')
}
//仍然和react原先的思路差不多
return(
<div>
<button onClick = {this.bindClick}>demo1</button>
<button onClick = {this.bindClick2.bind(this)}>demo2</button>
</div>
)
}
export default BindThisDemo
第二种:onClick = {() => this.yourEvent()},使用箭头函数(推荐),且还可以获取一些参数信息:比如event对象
import React from 'react'
function BindThisDemo(){
bindClick(){
alert('绑定this')
}
bindClick2(){
alert('绑定this')
console.log(e.value)//demo2
}
//仍然和react原先的思路差不多
return(
<div>
<button onClick = {()=>this.bindClick()}>demo1</button>
<button onClick = {(e)=>this.bindClick2()}>demo2</button>
</div>
)
}
export default BindThisDemo
好了,这就是个人关于绑定this引发的一点小小思考,错误的地方肯定不少,欢迎指出!
参考文章:
深入解读 React 组件中的 "this" - 知乎 (zhihu.com)
You-Dont-Know-JS/ch2.md at 1ed-zh-CN · getify/You-Dont-Know-JS (github.com)
Function.prototype.bind() - JavaScript | MDN (mozilla.org)
Vue实例里this的使用 - 掘金 (juejin.cn)
React 中的 this 指向 - 掘金 (juejin.cn)
深入浅出React(一):React的设计哲学 - 简单之美移动王沛_InfoQ精选文章
vue中的this指向问题 - 知乎 (zhihu.com)
向原作者一并致谢!