this 深度解析
this 深度解析
这是《你不知道的JavaScript》第三节,深入了解 this 的含义,以及如何判断 this 的指向。
this 是什么
首先我们要知道 this 到底是什么。我认为,this 更像是一个关键字,他最终可能会指向某个对象(也有可能指向undefined)。在函数中,this 只在函数调用的时候才确定其最终指向的对象,当我们在函数中对 this 进行操作的时候,实际是操作 this 指向的变量。
这里会有一些误区:
误区1:this 指向函数本身,这种想法是错的,虽然从 this 的字面含义的确有这层意思。
误区2:this 指函数的作用域,这是初学者比较容易弄混 this 和作用域的概念。诚然在某种情况下,this 可能会指向作用域,但这不代表 this 是函数的作用域。
用简单的示例你就能明白
function a() {
let name = 'this'
console.log(this.name)
this.age = 20
console.log(a.age)
}
a()
// log:
// undefined
// undefined
如果 this 指向函数,那么 this.age = 20
中 age
最终应该会挂载到 a.age
中。如果 this 指向函数作用域,那么 this.name
应该可以访问到作用域中定义的 name
值。但实际都无法获取到。
this 指向
那么 this 实际指向谁,只需遵循下面几个原则,你就能很快判断 this 指向谁
- 隐式绑定
- 显式绑定
- new
- 都没有,则是默认绑定
一般情况下,如果不满足前三种绑定,那么就是默认绑定,我们一个个来说。
隐式绑定
隐式绑定最简单的判断就是是否有调用者,如:obj.log()
;其中 log 是执行的函数,obj 是log的调用者。这种情况下,log 函数执行时 this 指向 obj
这里你需要注意,不一定非要将 log 函数定义在 obj 内。只要满足 log 的调用者是 obj 即可。这是我们可能会忽略的地方。如:
let b = {
name: 'bbbb',
log: function() {
console.log(this.name)
}
}
let obj = {
name: 'aaaa',
log: _log
}
obj.log() // aaaa
你不需要太过注重函数定义的位置(一般情况下),因为决定 log 函数中 this 指向的是他实际的调用者。
显式调用
显式调用是指使用call
,apply
,bind
等方法强制改变函数执行时 this 的指向。这里简单演示一下
function log() {
console.log(this.name)
}
let a = {
name: 'a'
}
log.call(a) // a 直接执行,第二个参数后接收变量,作为参数传入log
log.apply(a) // a 直接执行,第二个参数是一个数组,数组最终会展开传入log
log.bind(a)() //a 返回一个新的函数,第二个参数后接收变量,作为参数传入log
这里不详细讨论这些方法的用法,这不是我们的重点。
如何隐式和显式一起调用,那么最终 this 指向谁呢?
let a = {
name: 'a',
log() {
console.log(this.name)
}
}
let b = {name: 'b'}
a.log.call(b) // ?
// 实际最终打印 b
显式调用的优先级高于隐式调用
new
在我们使用构造函数 new 一个对象的时候,函数最终会返回一个对象,而函数内对 this 的操作会最终执行到这个对象。
这实际是因为,构造函数在 new 的时候,会新建一个空的对象,并将函数的 this 指向这个新建的对象,所以函数内对 this 的操作,实际就是对这个新的对象的操作。构造函数在执行的时候实际可以指定需要指向的对象,但一般用的不多,这里不做讨论。
function Foo(name) {
this.name = name
}
let foo = new Foo('foo')
// {name: 'foo'}
默认绑定
如果不满足上面三种情况,那么函数中 this
会进行默认绑定到 window
(非严格模式下),如果是严格模式下,会绑定 undefined
。
function log() {
console.log(this)
}
log() // Window
// 严格模式
'use strict'
function log() {
console.log(this)
}
log() // undefined
箭头函数
对于箭头函数,他内部的this指向,他并不适用于用上面四种情况来判断。因为严格意义来说箭头函数内部并没有this。若在箭头函数内调用 this
,this
最终指向其定义该箭头函数时父级作用域的 this
。类似词法作用域,所以箭头函数的 this
在定义该箭头函数的时候就已经确定,且无法改变。所以在使用箭头函数的时候需要注意其 this
的指向。
let log = null
let obj = {
far() {
log = () => {
// 箭头函数 this 指向父级 this
console.log(this)
}
}
}
obj.far() // 当 far 执行的时候定义了log箭头函数,且此时 far 函数 this 指向 obj
log() // log 是一个箭头函数,其 this 指向父级函数的this,也就是 obj
箭头函数的 this
这种继承的效果,可以让我们更好的处理某些函数中需要使用特定 this
对象的情况。但这同时也会引发一些问题,如 this
强制绑定,这是需要注意的。