class 中 this 的行为和函数中的类似(毕竟类的底层依然是函数),但是它们之间还是有一些区别的。
在类的构造函数中,this 是一个常规对象,类里面的所有非静态方法都会添加到这个 this 的原型上。而类里面的所有静态方法都不是这个 this 的属性,而是类本身的属性。
class Obj {
constructor() {
this.a = 'inside';
const proto = Object.getPrototypeOf(this);
console.log(Object.getOwnPropertyNames(proto)); // ['constructor', 'method1']
}
method1() {
console.log(this.a);
}
static method2() {
console.log(this.a);
}
}
const o = new Obj();
o.method1(); // 'inside'
o.method2(); // TypeError: o.method2 is not a function
Obj.method2(); // undefined
有时可能需要手动修改类方法里的 this 值,以确保它始终指向特定类的实例。比如:
class Car {
constructor() {
this.name = 'Car';
this.sayBye = this.sayBye.bind(this); // sayBye() 方法将始终指向 Car 的实例
}
sayHi() {
console.log('Hi,', this.name);
}
sayBye() {
console.log('Bye,', this.name);
}
}
class Bird {
constructor() {
this.name = 'Bird';
}
}
const car = new Car();
const bird = new Bird();
bird.sayHi = car.sayHi;
bird.sayHi(); // Hi, Bird
bird.sayBye = car.sayBye;
bird.sayBye(); // Bye, Car
1.4 派生类
和基类的构造函数不同,派生类的构造函数没有初始的 this 绑定,所以就需要开发人员在构造函数中手动调用 super()。比如,执行以下代码会报错:
class Base { }
class Child extends Base {
constructor() { }
}
// ReferenceError:
// Must call super constructor in derived class
// before accessing 'this' or returning from derived constructor
new Child();
在构造函数中调用 super() 的时候,会创建一个 this 绑定。这本质上就和执行了语句 this = new Base() 的效果类似,其中 Base 是继承的父类。
class Base { }
class Child1 extends Base { } // 没有 constructor()
class Child2 extends Base {
constructor() {
return {}; // constructor() 自己返回一个对象
}
}
new Child1();
new Child2();
2. 调用方式
2.1 作为对象方法
当一个函数作为对象的方法被调用时,它的 this 值会被设置成调用该方法的对象。
const o = {
a: 34,
f() {
return this.a;
}
};
console.log(o.f()); // 输出 34,因为当执行 f() 时,其内部的 this 会被绑定到 o 对象
只要函数是作为对象的方法被调用的,那就符合这样的逻辑:函数的 this 值会被设置成调用该方法的对象,而不管函数是怎么定义的或是在哪里定义的。
2.1.1 先单独定义函数
也可以先定义函数,然后再将其附加到对象的属性上,最终效果都是一样的。
function fn() {
return this.a;
}
const o = {
a: 34
};
o.f = fn;
console.log(o.f()); // 依然是输出 34,因为 f() 还是作为对象 o 的方法被调用的
当一个函数被当作构造函数使用时(使用 new 关键字),它的 this 值会绑定到正在构造的新对象上。
function Func() {
this.a = 34;
}
let o = new Func();
console.log(o.a); // 34
然而,如果函数有自己的 return 语句,且返回的是一个对象,那么这个对象就会被作为 new 表达式的结果。如下:
function Func() {
this.a = 34;
return {
a: 40
}
}
let o = new Func();
console.log(o.a); // 40
在上面的例子中,因为函数在构造的过程中返回了一个对象(第 3 行),所以它就作为了 new 表达式的结果,因此第 9 行会输出 40,而不是 34。在这种场景下,Func() 函数里的 this 所指向的新对象并没有被暴露出去,这就意味着 this 对象只能在构造函数里使用,这在一定程度上实现了封装。
需要注意的是,一旦(构造)函数 return 的不是一个对象,那么 new 表达式的结果还依然是初始的 this 值(即正在被构造的新对象)。如下:
function Func() {
this.a = 34;
return 40; // 返回的是“原始值”而不是“对象”
}
let o = new Func();
console.log(o.a); // 会输出 34
对象 obj 的方法 bar() 返回的是一个箭头函数。因为是箭头函数,所以它的 this 值会永久绑定到其封闭上下文的 this 上——函数 bar() 的 this 值。而函数 bar() 的 this 值又是动态绑定的(和它的调用方式相关),这就反过来影响到了返回的箭头函数的 this 值,也就是说这里的箭头函数的 this 值看起来也是“动态”的了。