[关闭]
@wrlqwe 2016-07-04T04:13:06.000000Z 字数 3081 阅读 919

JavaScript中的this

JavaScript 开发


最近在学习ReactNative,免不了被坑在JS上,跟Java和Objective-C不同,JS方法里的this显得很另类,有点令人困惑。

什么是this

要说清this是什么,可以从Function.prototype.call()方法说起,这个方法近似于function调用的原始实现,它的方法签名是这样的:

  1. fun.call(thisArg[, arg1[, arg2[, ...]]])

其中thisArg代表的就是方法里的this,而其他参数,则是arg1arg2以可变长度参数的形式传递。调用的过程和结果如下:

  1. function hello(thing) {
  2. console.log(this + " says hello " + thing);
  3. }
  4. hello.call("Yehuda", "world") //=> Yehuda says hello world

JavaScript里的其他方法调用方式大致可以看做这个调用的语法糖[1]

在ES5前,function里的this默认会是window或者global对象,而使用了严格模式的ES5,全局function里的this将会是undefined

  1. // this:
  2. hello("world")
  3. // desugars to:
  4. hello.call(undefined, "world");

对于一个方法来说this并不是一成不变,具体是什么取决于调用这个方法的时机:

  1. function hello(thing) {
  2. console.log(this + " says hello " + thing);
  3. }
  4. person = { name: "Brendan Eich" }
  5. person.hello = hello;
  6. person.hello("world") // still desugars to person.hello.call(person, "world")
  7. hello("world") // "[object DOMWindow]world" //具体环境决定

通常我们把this形容为上下文就是因为这个。

那么问题来了

JavaScript中,我们经常要将方法传递给其他方法,当做回调函数,比如最近学习的ReactNative,JSX中经常要将方法作为参数传递:

  1. render() {
  2. return (
  3. <ListView dataSource={this.props.dataSource} renderRow={this._renderRow}
  4. />
  5. )
  6. }
  7. ...
  8. _renderRow(itemData) {
  9. return (
  10. <TouchableHighlight onPress={this._rowPressed} >
  11. ...not important
  12. </TouchableHighlight>
  13. )
  14. }
  15. _rowPressed() {
  16. ...not important
  17. }

细心的盆友看出来了,我这样写是不行的,ListView的renderRow方法是由react执行的,它调用_renderRow的时候,上下文this必然不是声明render方法所在的实例,_renderRow方法里的this._rowPressed必然是undefined

JavaScript中Function是一等类型,可以被显式传递,所以必然会出现上下文不对的问题,在没有深入研究过的开发者手中,往往忽视这个问题,写出大坑。

如何使this确定

bind

这个问题也困扰了JS开发者很久,如何解决呢?
在ES5之前,人们通过一个简单方法帮助改正错误的this:

  1. var person = {
  2. name: "Brendan Eich",
  3. hello: function(thing) {
  4. console.log(this.name + " says hello " + thing);
  5. }
  6. }
  7. var boundHello = function(thing) { return person.hello.call(person, thing); }
  8. boundHello("world");

尽管我们可以在任何地方调用boundHello方法,可是boundHello实际上是对上下文和原始hello方法进行了封装,所以最终hello里的this始终是person

这个方法最终被ES5引入的Function.prototype.bind方法所替代,所以现在我们的代码里可以这样使用:

  1. var boundHello = person.hello.bind(person);
  2. boundHello("world") // "Brendan Eich says hello world"

所以,前文里面困扰了我的ReactNative问题,解决方法也许是:

  1. render() {
  2. return (
  3. <ListView dataSource={this.props.dataSource} renderRow={this._renderRow.bind(this)}
  4. />
  5. )
  6. }
  7. ...
  8. _renderRow(itemData) {
  9. return (
  10. <TouchableHighlight onPress={this._rowPressed.bind(this)} >
  11. ...not important
  12. </TouchableHighlight>
  13. )
  14. }
  15. _rowPressed() {
  16. ...not important
  17. }

而考虑到ReactNative,render方法的性能要求,不能在render过程中创建新方法,所以最终解决方案是这样的:

  1. constructor() {
  2. super()
  3. this._renderRow = this._renderRow.bind(this)
  4. this._rowPressed = this._rowPressed.bind(this)
  5. }
  6. render() {
  7. return (
  8. <ListView dataSource={this.props.dataSource} renderRow={this._renderRow}
  9. />
  10. )
  11. }
  12. ...
  13. _renderRow(itemData) {
  14. return (
  15. <TouchableHighlight onPress={this._rowPressed} >
  16. ...not important
  17. </TouchableHighlight>
  18. )
  19. }
  20. _rowPressed() {
  21. ...not important
  22. }

另外bind(this)在ES6里还有新语法糖:

  1. this._renderRow = ::this._renderRow
  2. this._rowPressed = ::this._rowPressed

不过在Babel的实现中说明了这个语法是highly experimental的,所以最后用不用,follow your heart~

Fat Arrow

ES6引入了箭头匿名方法,它的方法体里的this是会绑定给初始化它的实例的,所以constructor里的方法也可以改为:

  1. this._renderRow = (rowData) => this._renderRow(rowData)
  2. this._rowPressed = () => this._rowPressed()

这种方法的缺点是参数个数是固化的,如果想处理原方法的更多参数,需要在这里显式写出这些参数,不太灵活,毕竟我们只想绑定this,不关心参数。

鸣谢

bind新语法
what's 'this'
jsx中不推荐使用bind和箭头函数
mozilla bind文档

谢谢收看


[1] 严格来说不是,只是近似,方便下文讲解。
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注