由绑定this引发的思考

118 阅读5分钟

深入浅出 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)

JSX绑定事件 - 掘金 (juejin.cn)

Function.prototype.bind() - JavaScript | MDN (mozilla.org)

Vue实例里this的使用 - 掘金 (juejin.cn)

React 中的 this 指向 - 掘金 (juejin.cn)

深入浅出React(一):React的设计哲学 - 简单之美移动王沛_InfoQ精选文章

vue中的this指向问题 - 知乎 (zhihu.com)

向原作者一并致谢!