5分钟彻底搞懂JavaScript中的this指向问题

1,424 阅读4分钟

在Javascript中,this的指向灵活,使用场景多,面试中会被经常提及。由于this指向的灵活性,会在开发过程中产生一些不容易发现的BUG。

技术社区通常用一句话总结了this的指向问题:谁调用它,this就指向谁。也就是说,this的指向是在调用时确定它究竟指向谁的。

通过社区的技术文章,有人总结出几条规律:

1.在函数体中非显式或隐式地简单调用函数时,在严格模式下,函数内的this会被绑定到undefined,在非严格模式下,则会绑定到全局对象 window/global上。 2.一般使用new方法调用构造函数时,构造函数内的this指向会被绑定到新创建的对象上。 3.一般通过call/apply/bind方法显式调用函数时,函数体内的this会被绑定到指定参数的对象上。 4.一般通过上下文对象调用函数时,函数体内的this会被绑定到该对象上。 5.在箭头函数中,this的指向是由外层(函数或全局)作用域来决定的。

我们各点进行分析下:

全局环境中的this

非严格模式下this指向window

function f () {
    console.log(this)  // window
}

那么上面的代码输出则应该是window

在严格模式下函数体内的this指向undefined


funciton f () {
    'use strict'
    console.log(this)   //undefined
}

通过use strict指明严格模式的情况下 this 执行则是undefined

下面看一道题目

const foo =  {
    bar: 10,
    fu: function() {
        console.log(this)
        console.log(this.bar)
    }
}
var fn1 = foo.fn

fn1 ()

fn 函数在foo对象中用来作为对象的方法,但是在赋值给fn1之后,fn1仍然在全局环境中执行,因此的打印结果就是

console.log(window)
console.log(window.bar)

如果把,这道题改为如下形式

const foo = {
    bar: 10,
    fu: function() {
        console.log(this)
        console.log(this.bar)
    }
}
fo.fn()

则输出的是

{bar: 10, fn: f}
10

这时,this指向的是最后调用它的对象,所以this指向的是foo对象,在执行函数时不考虑显式绑定,如果函数中的this是被上一级的对象调用,那么this指向的就是上一级的对象;否则指向全局环境。

上下文对象调用中的this

通过上面的结论我们分析下下面的代码

const person = {
    name: 'jujin',
    brother: {
        name: 'kejinan',
        fn: function () {
            return this.name
        }
    }
}
console.log(person.brother.fn())

fn 是被它的上一级调用,所以this 应该指向brother,所以 this.name ==== 'kejinan'

通过bind,call,apply改变this指向

用一句话总结,他们都是用来改变相关函数的this指向的,但是call和apply是直接进行函数的调用,bind不会执行相关函数,而是返回一个新的函数,并且自动绑定了新的this,需要手动调用。 用代码来总结,下面的3段代码是等价的

// 1
const target = {}
fn.call(target, 'arg1', 'arg2')

// 2 
const target = {}
fn.apply(target, ['arg1', 'arg2'])

// 3 
const target = {}
fn.bind(taget, 'arg1', 'arg2')()

下面我们来分析下这道题

const foo = {
    name: 'juejin',
    logName: function () {
        console.log(this.name)
    }
}
const bar = {
    name:"kejinan"
}
console.log(foo.logName.call(bar))

通过call 将this指向绑定到了bar对象上,则输出结果是kejinan

构造函数和this

function Foo() {
    this.bar = 'bar'
}
const instance = new Foo()
console.log(instace.bar)

执行会输出bar,需要注意的是,在构造函数中如果出现了return的情况时,可以分为两种场景:

//场景1
function Foo() {
    this.user = 'juejin'
    const o = {}
    return o
}
const instance = new Foo()
console.log(instance.user)

执行代码则会输出undefined,此时instance返回的对象是控对象o.

//场景2
function Foo(){
    this.user = 'juejin'
    return 1
}
const instance = new Foo()
console.log(instance.user)

执行代码将会输入jueji,也就是说instance返回的目标对象是实例this。 所以,如果构造函数中显式返回一个值,且返回的是一个对象,那么this就执行的这个返回的对象,如果返回的不是一个对象,那么this仍然指向实例。

箭头函数中的this

在箭头函数中,this指向的是由外层作用域来决定的

const foo = {
    fn: function () {
        setTimeout(function() {
            console.log(this)
        })
    }
}
console.log(foo.fn())

在这段代码中,this出现中setTimeout()的匿名函数中,因此this指向的是window对象。

如果需要让this指向foo对象,则可以用箭头函数来处理

const foo = {
    fn: function () {
        setTimeout(() => {
            console.log(this)
        })
    }
}
console.log(foo.fn())

总结

通过本篇内容的学习,我们看到this的使用场景繁多,确实不容易彻底掌握。根据文中提到的5点总结再结合代码理解起来容易很多,只有死记才能用活。

参考资料

  • 前端开发核心知识进阶-从夯实基础到突破瓶颈 - 侯策 - 中国工信出版集团 ISBN 978-7-121-38934-4