this到底指向谁!!

157 阅读3分钟

前言

面试中,经常会被面试官从多个角度考查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: ƒ}