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()
生成。
网友评论