JavaScript中
this关键字的指向是一个让初学者(比如我...)很头疼的问题。本文的内容主要出自阮一峰老师JavaScript教程this 关键字一节,读完受益良多,在此简要总结,方便日后回顾。
1. this关键字的意义
在理解this指向问题之前,需要先了解设计这个关键字的初衷。在JavaScript中,存储引用类型(广义的对象)值的变量中存放的是地址。访问对象属性时,首先从变量获取对象地址,然后再从该地址读取对应的属性,此时属性所处的上下文(环境)就是当前对象。
如果对象的属性是一个函数,那么该属性本身存储的是函数的地址。调用对象方法时,先从变量获取对象地址,再从对象地址中读取方法地址,再调用函数,函数调用时的环境也是当前对象。
函数作为一个值,可以在不同的上下文中执行,而this关键字的作用正是用来指代函数执行时所处的环境。
const obj = {
a: 1,
b: function() {
console.log(this.a)
}
}
obj.b() // 1
上面这段代码的执行过程可以描述为:
- 先通过变量
obj获取对象地址 - 再从对象地址获取函数
b的地址 - 通过函数
b的地址调用函数,this指向当前环境,也就是obj对象,因此this.a就是1
2. this关键字的指向
this指向的就是引用this的环境(或者称之为对象)。
2.1 全局作用域
在全局作用域中,this指向window对象(浏览器环境)。还是上面的例子:
const obj = {
a: 1,
b: function() {
console.log(this.a)
console.log(this)
}
}
window.a = 2
const temp = obj.b
temp() // 2 window {...}
obj.b() // 1 {a: 1, b: f}
可以看出,如果将obj.b函数赋值给变量temp,则变量temp存储了该函数的地址,调用temp()是在全局环境中直接获取了函数的地址,因此this指向window对象。而通过对象obj调用函数时,函数内部的this指向obj。
2.3 构造函数
构造函数中的this指向通过new关键字调用构造函数的实例对象。
2.3 对象的方法
对象方法中的this指向方法执行时所在的对象。
如果对象的属性还是一个对象,那么嵌套的对象的方法中的this指向嵌套的对象,而不是最外层的对象。看下面这个例子:
const obj = {
a: 1,
b: {
m: function() {
console.log(this.a)
}
}
}
obj.b.m() // undefined
调用obj.b.m()时,其中的this指向属性b这个对象,因此this.a为undefined。
2.4 箭头函数
ES6新增了箭头函数语法。箭头函数没有this,如果在箭头函数中使用this,就当做一个普通的变量,会按照作用域规则从当前作用域向上级作用域逐级查找,直到全局作用域对象window。
const obj = {
a: 1,
test: function() {
console.log(this.a)
}
}
obj.test() // 1
正常函数的this指向函数调用时的环境。
window.a = 2
const obj = {
a: 1,
test: () => {
console.log(this.a)
}
}
obj.test() // 2
箭头函数中的this就是一个普通变量,由于test()方法被调用时处于obj的环境且找不到this变量,会继续向上一级作用域也就是全局作用域查找,全局作用域中this就是window对象,因此最终打印结果为2。
3. 改变this指向
this随函数执行环境动态切换,虽然具有极强的灵活性,但也造成了this指向不明朗的困惑。在需要固定this指向的时候,可以通过以下三个定义在Function.prototype中的方法。
3.1 call()方法
基本用法为func.call(thisValue, arg1, arg2, ...)。其中,第一个参数是函数func执行时其内部this指向的作用域对象,剩余参数可选,为传入函数func的参数。
call()方法的一个重要的应用是调用对象的原生方法,例如将字符串转换为数组:
Array.prototype.slice.call('123') // ["1", "2", "3"]
以及将类数组对象转换为真正的数组:
Array.prototype.slice.call({0: 'a', 1: 'b', 2: 'c', length: 3}) // ["a", "b", "c"]
3.2 apply()方法
用法与call()类似,唯一的区别在于函数的参数以数组形式传入,即func.apply(thisValue, [arg1, arg2, ...])
3.3 bind()方法
call()和apply()方法在被函数调用之后,会立即执行函数。但有时候我们不需要函数被立刻执行,而是需要返回一个新函数,可以使用bind()方法。看下面的例子:
const counter = {
count: 0,
inc: function() {
this.count++
console.log(this)
}
}
function add(callback) {
callback()
}
add(counter.inc) // Window {...}
counter.count // 0
将counter对象的inc方法作为回调函数传入add函数,在调用add函数时,inc方法内部的this指向的是add方法被调用时所处的环境,也就是window对象。因此counter对象的count属性还是0。
此时可以使用bind()将inc方法内部的this指向固定为counter对象:
add(counter.inc.bind(counter)) // {count: 1, inc: ƒ}
counter.count // 1
需要注意的是,函数没调用一次bind()方法,就返回一个新函数。如果这个新函数在多个地方被使用,应该提前将其缓存下来,而不是在每一个使用到的地方通过bind()生成。