只要函数是作为对象的方法被调用的,那就符合这样的逻辑:函数的 this 值会被设置成调用该方法的对象,而不管函数是怎么定义的或是在哪里定义的。
2.1.1 先单独定义函数
也可以先定义函数,然后再将其附加到对象的属性上,最终效果都是一样的。
2.1.2 嵌套对象的方法
也可以将函数赋值给嵌套 n 层的对象的属性,此时 this 值的原理也是一样的。
2.1.3 原型链上的方法
同样的逻辑,也适用于定义在对象原型链上的方法。如果一个方法位于对象的原型链上,那么其 this 指向的依然是调用该方法的特定对象(就像该方法就在那个对象上一样)。
虽然分配给变量 p 的对象没有自己的 sum 属性,但它可以从其原型链上继承。
p.sum() 对 sum 的查找是从对象 p 开始的,所以 sum() 函数内部的 this 值会被设置成对象 p 的引用。也就是说因为 sum() 是作为对象 p 的方法被调用的,所以它的 this 指的就是对象 p。这是 JavaScript 原型继承中一个非常有趣的特性。
2.1.4 getter 和 setter
同样,当函数作为 getter 和 setter 被调用时,其内部的 this 会绑定到获取和设置属性的对象上。
2.2 作为构造函数
当一个函数被当作构造函数使用时(使用 new 关键字),它的 this 值会绑定到正在构造的新对象上。
然而,如果函数有自己的 return 语句,且返回的是一个对象,那么这个对象就会被作为 new 表达式的结果。如下:
在上面的例子中,因为函数在构造的过程中返回了一个对象(第 3 行),所以它就作为了 new 表达式的结果,因此第 9 行会输出 40,而不是 34。在这种场景下,Func() 函数里的 this 所指向的新对象并没有被暴露出去,这就意味着 this 对象只能在构造函数里使用,这在一定程度上实现了封装。
需要注意的是,一旦(构造)函数 return 的不是一个对象,那么 new 表达式的结果还依然是初始的 this 值(即正在被构造的新对象)。如下:
2.3 箭头函数
箭头函数没有绑定自己的 this 值,它会直接保留封闭词法上下文的 this。关于箭头函数的基本介绍详见《箭头函数》,这里只重点介绍一些交叉类信息,同时作为对箭头函数的一个补充。
2.3.1 在函数内嵌套
如果是在其它函数内部创建的箭头函数,它的 this 值依然只和封闭词法上下文的 this 相关。
以上代码的执行结果,如下:
对象 obj 的方法 bar() 返回的是一个箭头函数。因为是箭头函数,所以它的 this 值会永久绑定到其封闭上下文的 this 上——函数 bar() 的 this 值。而函数 bar() 的 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
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
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();
class Base { }
class Child1 extends Base { } // 没有 constructor()
class Child2 extends Base {
constructor() {
return {}; // constructor() 自己返回一个对象
}
}
new Child1();
new Child2();
const o = {
a: 34,
f() {
return this.a;
}
};
console.log(o.f()); // 输出 34,因为当执行 f() 时,其内部的 this 会被绑定到 o 对象
function fn() {
return this.a;
}
const o = {
a: 34
};
o.f = fn;
console.log(o.f()); // 依然是输出 34,因为 f() 还是作为对象 o 的方法被调用的