this 指向问题

128 阅读3分钟

「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战

JavaScript 中的 this 在严格模式和非严格模式下的表现方式不同,再加上修改 this 值的函数和箭头函数的引入,this 它糊了!!!

我们根据 this 使用的场景入手,来理一下。

哪些地方会用到 this?

想想 this 出现的地方,主要场景有 3 种,分别是:

  • 全局中的 this

    全局的 this 无论是否为严格模式都指的是 window 对象。

  • 函数中的 this

    函数中的 this 一般是指函数调用者,但是因为箭头函数和严格模式的存在,还有 bind 插一手就,this 有不同的取值方式。函数也是主战场,我们后面重点介绍。

  • 类中的 this

    类本质也是函数,只是类方法按严格模式设计。类中的 this 有一些特殊情况。

函数中的 this

ECMA规范中有说到,当函数初始化时会调用方法 FunctionInitialize (F,  kind, ParameterList,  Body,  Scope), 参数 Kind 有3个可选值,分别是 Normal(普通函数),Method(方法),Arrow(箭头函数)。

每个函数都有一个叫 [[ThisMode]] 的私有属性,函数初始化时会根据不同的情况设置这个属性:

  • 当 Kind 为 Arrow 时,[[ThisMode]] 会设置为 lexical,函数调用时从上下文中取 this 值;

  • 当参数 F 是严格模式时,[[ThisMode]] 会设置为 strict,这时的 this 是函数调用者,调用者可能会是 undefined;

  • 除了上面两种情况外,[[ThisMode]] 会设置为 global,这时的 this 是个 undefined,非严格模式下会指向全局对象 window;

普通函数-非严格模式

普通函数非严格模式下 [[ThisMode]] 为 global,所以 this 指向全局对象 window。如果把这个普通函数赋值给对象的属性再调用,则 this 指向调用的对象。

所以同一个函数,调用的方式不一样,this 的取值也不同(运行时绑定)。

function showThis() {
    console.log(this);
}
var o = {
    showThis: showThis
}

showThis(); // global
o.showThis(); // o

普通函数-严格模式

严格模式下的 [[ThisMode]] 为 strict,this 指向调用者,如果没有调用者则 this 为 undefined。

function showThis() {
    "use strict"
    console.log(this);
}

showThis(); // undefined

箭头函数

箭头函数的 [[ThisMode]] 为 lexical,从执行上下文中获取 this。


const showThis = () => {
    console.log(this);
}

var o = {
    showThis: showThis
}

showThis(); // global
o.showThis(); // global

方法

方法的实现方式和普通函数的严格模式一样,所以表现方式和普通函数严格模式一样。

class C {
    showThis() {
        console.log(this);
    }
}
var o = new C();
var showThis = o.showThis;

showThis(); // undefined
o.showThis(); // o

类中的 this

  • 类的构造函数中的 this 是一个常规对象,所有类的非静态方法都会添加到 this 的原型中;
class Foo {
    constructor() {
        const proto = Object.getPrototypeOf(this)
        console.log(Object.getOwnPropertyNames(proto))
    }
    
    foo (){}
    static foo2(){}  //静态方法不会加到 this 的原型中
}

new Foo()  //['constructor', 'foo']
  • 在派生类中
    • 如果没有调用 super() 方法之前不能使用 this,super() 相当于执行了 this = new Base()
    • 派生类如果有构造函数,必须调用 super() 或返回一个对象;
class Base {}
class Good extends Base {} //没有构造函数
class Good2 extends Base {  //构造函数返回对象
    constructor() {
        return {}
    }
}
class Good3 extends Base {  //构造函数调用 super()
    constructor() {
        super()
    }
}
class Bad extends Base {  //有构造函数,既没有调用 super() 也没返回对象,new 的时候报错
    constructor() {}
}

new Good()
new Good2()
new Good3()
new Bad() // Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

改变 this 的方法

改变 this 的方法有 3 个,call、apply、bind,我们下期再来聊一聊。

如有错误欢迎指出,欢迎一起讨论!