您的位置:首页 > Web前端 > JavaScript

JavaScript中的this

2016-07-04 11:02 645 查看
什么是this

那么问题来了

如何使this确定
bind

Fat Arrow

鸣谢

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

什么是
this

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

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


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

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


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

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


// this:
hello("world")
// desugars to:
hello.call(undefined, "world");


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

function hello(thing) {
console.log(this + " says hello " + thing);
}

person = { name: "Brendan Eich" }
person.hello = hello;

person.hello("world") // still desugars to person.hello.call(person, "world")

hello("world") // "[object DOMWindow]world" //具体环境决定


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

那么问题来了

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

render() {
return (
<ListView dataSource={this.props.dataSource} renderRow={this._renderRow}
/>
)
}
...

_renderRow(itemData) {
return (
<TouchableHighlight onPress={this._rowPressed} >
...not important
</TouchableHighlight>
)
}

_rowPressed() {
...not important
}


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


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

如何使
this
确定

bind

这个问题也困扰了JS开发者很久,如何解决呢?

在ES5之前,人们通过一个简单方法帮助改正错误的this:

var person = {
name: "Brendan Eich",
hello: function(thing) {
console.log(this.name + " says hello " + thing);
}
}
var boundHello = function(thing) { return person.hello.call(person, thing); }
boundHello("world");


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


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

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


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

render() {
return (
<ListView dataSource={this.props.dataSource} renderRow={this._renderRow.bind(this)}
/>
)
}
...

_renderRow(itemData) {
return (
<TouchableHighlight onPress={this._rowPressed.bind(this)} >
...not important
</TouchableHighlight>
)
}

_rowPressed() {
...not important
}


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

constructor() {
super()
this._renderRow = this._renderRow.bind(this)
this._rowPressed = this._rowPressed.bind(this)
}
render() {
return (
<ListView dataSource={this.props.dataSource} renderRow={this._renderRow}
/>
)
}
...

_renderRow(itemData) {
return (
<TouchableHighlight onPress={this._rowPressed} >
...not important
</TouchableHighlight>
)
}

_rowPressed() {
...not important
}


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

this._renderRow = ::this._renderRow
this._rowPressed = ::this._rowPressed


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

Fat Arrow

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

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


这种方法的缺点是参数个数是固化的,如果想处理原方法的更多参数,需要在这里显式写出这些参数,不太灵活。

鸣谢

bind新语法

what’s ‘this’

jsx中不推荐使用bind和箭头函数

mozilla bind文档

谢谢收看

杏树林研发 王儒林
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  javascript