Know Everything About This
这是一篇目前篇幅不长,但是写起来相当漫长的文章。中途,我翻译了这篇文章,非常有必要一读。
一、是什么?
「在函数内部,有两个特殊的对象:arguments和this」
你可能见过这样一个函数:
function addUp(){
return Array.from(arguments).reduce(function (prev, curr) {
return prev + curr
})
}
并没有定义过它,但是arguments
直接就这么冒出来了。this
和它一样,它们自动定义在所有函数的作用域中,即使你并没有用它。
NZ在《JavaScript高级程序设计》里给this
下了个定义:「this引用的是函数据以执行的环境对象」。这就像是很多人强调的,不要看函数在哪里定义,要看函数在哪里被调用。
二、为什么?
this
机制可以解耦对象和函数:
function identify () {
return this.name.toUpperCase()
}
function speak () {
var greeting = "Hello,I'm + identify.call(this)"
console.log(greeting)
}
var me = {
name: 'cyq'
}
var you = {
name: 'reader'
}
identify.call(me) // CYQ
identify.call(you) // READER
speak.call(me) // Hello,I'm CYQ
speak.call(you) // Hello,I'm READER
这段代码可以在不同的上下文对象中重复使用identify
和speak
,不用针对每个对象编写不同版本的函数。如果没有this
,就需要给函数显式地传递一个上下文对象。随着你的模式越来越复杂,显式传递上下文会让代码越来越混乱。当涉及到原型委托的时候,你就会明白函数可以自动引用合适的上下文对象有多重要。
三、怎么绑?
The keyword “this” refers to whatever is left of the dot at call-time.
If there’s nothing to the left of the dot, then “this” is the root scope (e.g. Window).
A few functions change the behavior of “this”—bind, call and apply
The keyword “new” binds this to the object just created
如果看过了这个四条法则,那判断this
指向就好办了啊,倒过来看就好了:
函数是否被new修饰(new绑定)?如果是的话,
this
绑定的就是新创建的对象函数是否通过call、apply()或者bind()硬绑定(显式绑定)?如果是的话,
this
绑定的就是指定的对象。函数是否在某个上下文对象调用(隐式绑定)?如果是的话,
this
绑定的就是那个对象。如果都不是的话,使用默认绑定。如果在严格模式下,就绑定
undefined
,否则是全局对象。
这可能是这篇文章最核心的地方,但是鉴于这四条太常见了,我假设读者已经知道它们了。
一、陷阱
1. 指向自身
把
this
指向函数自身,从语法角度来说是说得通的
function foo(num) {
this.count++
}
foo.count = 0
for(let i = 0;i < 10;i++) {
if(i > 5) {
foo(i)
}
}
console.log(foo.count) // 0
如果this
指向的是函数自身的话,foo.count
就该是4而不是0了。仔细看看就能明白这里是符合第四条的默认绑定的。
2. 指向词法作用域
不要看它是在哪定义的,也不要看它是在哪被调用的,看它是怎么被调用的
function foo() {
let a = 2
bar()
}
function bar() {
console.log(this.a)
}
foo() // ReferenceError: a is not a defined
对照第四条,默认绑定。
每当你想要把this
和词法作用域的查找混合使用时,记得提醒自己,这是没法实现的,箭头函数例外。
3. 隐式丢失
隐式丢失通常包括这几种情况:
赋值运算符 : (f = foo.bar)()
逗号运算符 : (1, foo.bar)()
回调函数
这一点可以说是一个庞大的工程,为此我翻译了这篇博客。翻译这篇博客这是件非常累的事儿,幸运的是,看完这篇博客之后,我明白了很多我没想到的东西。
所有需要了解的东西都可以在这篇博客里找到答案。
二、问题
1. self = this是什么?
我们知道,当代码进入一个函数时,作用域会把函数的参数、this 对象和 arguments对象包括在内。因此我们不可能在一个函数里面用this取到其他函数作用域内的this对象。
var object = {
name: 'My object',
getName: function() {
return function() {
return this.name
}
}
}
console.log(object.getName()())
这段代码会输出的是什么?虽然看上去很像是’My Object’,但是结果是undefined
.原因上面提到了:最内层的匿名函数的作用域内的this
和getName的this
是两回事。那想要输出’My Object’的话,自然要想办法把getName的this绑定到匿名函数下:
var me = {
name: 'cyq',
getName: function() {
var self = this
return function() {
return self.name
}
}
}
console.log(object.getName()())
可以看到,self=this
常用的场景就是帮我们在一个内层的函数里找到外层函数的this对象。我们需要把外层的this绑定在一个变量上,从而让内层函数完成某种类似于词法作用域的查找。我个人是很不喜欢这个写法的,后来我发现我不是一个人:搞懂JavaScript的Function.prototype.bind。
如果你了解过箭头函数的话,你可能知道箭头函数可以淘汰掉这种写法。
2. 箭头函数
虽然少打好几个字符就已经足够让人开心了,但是箭头函数的能力还不止于此。
var me = {
name: 'cyq',
getName: function() {
return () => {
return this.name
}
}
}
console.log(object.getName()()) //cyq
所以箭头函数一定对它里面的this
做了重新设计咯?答案是否定的。
箭头函数能实现类似词法作用域的查找是因为,它里面根本就没有this
对象。前面提到,this
自动定义在所有函数的作用域内,内嵌函数是不能查找到外部的this
对象的,因为自己的作用域里就有this
了。而箭头函数这个特殊的函数直接去作用域里找this
。