前言
面试中,经常会被面试官从多个角度考查this的理解:全局环境下的this、箭头函数中的this、构造函数中的this、this的显隐性和优先级等。有一种广为流传的说法是“谁调用它,this就指向谁”,也就是说,this的指向是在调用时确定的。这样说没什么大问题,但不全面。
关于this的指向有以下几条规律,需要“死记硬背”
- 在函数体中,非显示或隐式调用函数,在严格模式下,函数内的this指向undefined,在非严格模式下,this指向window/global
- 上下文对象调用函数,函数内的this指向该对象
- 通过call/apply/bind方法显示调用函数,函数内的this指向指定参数对象
- 使用new方法调用构造函数,构造函数内的this指向创建的实例对象
- 箭头函数中,this的指向是由外层(函数或全局)作用域决定
例题1:全局环境中的this
function f1() {
console.log(this)
}
function f2() {
'use strict'
console.log(this)
}
f1() // window
f2() // undefined
全局环境中调用函数,在非严格模式下,this指向window,在严格模式下,this指向undefined
const foo = {
bar: 10,
fn: function() {
console.log(this)
console.log(this.bar)
}
}
var fn1 = foo.fn
fn1() // window undefined
this仍然指向window。虽然fn函数在foo对象中作为对象的方法,但是在赋值给fn1后,fn1仍然是在window的全局环境中执行的。
例题2:上下文对象调用函数
const student = {
name: 'lilei',
fn: function() {
console.log(this)
}
}
student.fn() // this === student
const person = {
name: 'lilei',
brother: {
name: 'dalei',
fn: function() {
return this.name
}
}
}
person.brother.fn() // dalei (this指向最后调用它的对象)
// 高阶题目
const o1 = {
text: 'o1',
fn: function() {
return this.text
}
}
const o2 = {
text: 'o2',
fn: function() {
return o1.fn()
}
}
const o3 = {
text: 'o3',
fn: function() {
var fn = o1.fn
return fn()
}
}
console.log(o1.fn()) // o1 (o1调用,this指向o1)
console.log(o2.fn()) // o1 (最终调用的还是o1.fn(),所以this指向o1)
console.log(o3.fn()) // undefined 通过var fn=o1.fn的赋值进行了调用,因此这里的this指向了window,所以运行结果是undefined
例题3:call、apply、bind改变this指向
call、apply、bind都是用来改变相关函数的this指向,但是call、apply会直接执行相关函数的调用,bind不会执行相关函数的调用,而是返回一个新的函数,这个函数绑定了新的this指向,需要手动调用。再有的区别就是入参的差异,apply接受一个数组(或者类数组),call可以接受多个参数。
function person(name = 'lilei', age = '18') {
this.name = name
this.age = age
}
const target1 = {}
person.call(target1, '小三', 18)
const target2 = {}
person.apply(target2, ['小四', 19])
const target3 = {}
person.bind(target3, '小五', 20)()
console.log(target1) // {name: '小三', age: 18}
console.log(target2) // {name: '小四', age: 19}
console.log(target3) // {name: '小五', age: 20}
const foo = {
name: 'hanmeimei',
fn: function() {
console.log(this.name)
}
}
const bar = {
name: 'lilei'
}
foo.fn.call(bar) // lilei
例题4:构造函数和this
function Person() {
this.name = 'lilei'
}
const person = new Person()
console.log(person.name) // lilei
new操作符调用构造函数的时候具体做了什么?
- 创建了一个新的对象
- 将新对象的__proto__指向构造函数的prototype
- 将构造函数的this指向新对象
- 返回新对象
function Foo() {
this.user = 'lilei'
return {name: 'lilei'}
}
const foo = new Foo() // foo === {name: 'lilei'}
console.log(foo.user) // undefined
function Foo() {
this.user = 'lilei'
return 1
}
const foo = new Foo() // foo === {user: 'lilei'}
console.log(foo.user) // lilei
如果构造函数中显式返回一个值,且返回的值是一个对象,那么this就指向这个返回的对象;如果返回的不是一个对象(基本类型),那么this指向生成的实例对象
例题5:箭头函数中的this
const foo = {
fn: function() {
setTimeout(function() {
console.log(this)
})
}
}
foo.fn() // window (this出现在setTimeout的匿名函数中,因此this指向window)
const foo = {
fn: function() {
setTimeout(() => {
console.log(this)
})
}
}
foo.fn() // {fn: ƒ}