Kodokon kodokon.com

this、原型与类

理解 this 是如何动态解析的、原型链是如何工作的,以及类到底给对象模型带来了什么。

10 分钟 · 3 题

在 Kodokon 中打开本课

this 的值是在调用点决定的,而不是在声明时决定的。四条规则,按优先级从高到低排列:用 new 调用、显式绑定(callapplybind)、作为方法调用(obj.fn()),以及普通调用(fn()),后者在严格模式下会得到 undefined。最常见的现实 bug 由此直接产生:把一个方法从其对象上提取出来,会破坏隐式绑定。

JAVASCRIPT
const user = {
  name: "Ada",
  greet() {
    return "Hello, " + this.name;
  },
};
console.log(user.greet()); // "Hello, Ada"
const detached = user.greet;
// detached(); // TypeError: this is undefined
const bound = user.greet.bind(user);
console.log(bound()); // "Hello, Ada"
方法一旦被提取出来,隐式绑定就消失了。

箭头函数没有自己的 this:它们像闭包一样,从外层作用域按词法捕获 this。这使它们非常适合用作回调,但用作对象字面量上的方法则适得其反,因为那样 this 会指向外层作用域。在底层,每个对象都通过其原型链进行委托:一个缺失的属性会沿着链条逐环查找,直到到达 null

JAVASCRIPT
function Animal(name) {
  this.name = name;
}
Animal.prototype.describe = function () {
  return this.name + " is an animal";
};
const dog = new Animal("Rex");
console.log(dog.describe()); // "Rex is an animal"
console.log(Object.getPrototypeOf(dog) ===
  Animal.prototype); // true
console.log(dog.hasOwnProperty("describe")); // false
describe 存在于原型上,被每个实例共享。

class 语法并不改变这个模型:它只是让模型更易读。在类体中声明的方法会落到 Constructor.prototype 上,因此是共享的。实例字段(包括作为字段赋值的箭头函数)则会每个实例各自重建一份this 绑定得到保证,但内存开销成倍增加。最后,#private 字段提供了由引擎强制执行的私有性,这与 _下划线 约定不同。

JAVASCRIPT
class BankAccount {
  #balance = 0;
  deposit(amount) {
    if (amount <= 0) throw new Error("Invalid amount");
    this.#balance += amount;
    return this;
  }
  get balance() {
    return this.#balance;
  }
}
const account = new BankAccount();
account.deposit(50).deposit(25);
console.log(account.balance); // 75
由引擎强制执行的私有字段,以及一个可链式调用的 API。

知识检测

确认你已牢记本课的重点内容。

  1. 在严格模式下,进行一次没有对象、也没有显式绑定的普通调用 fn() 时,this 是什么?
    • globalThis
    • undefined
    • 声明 fn 时所在的那个对象
    • null
  2. 在类体中声明的方法(字段除外)被存储在哪里?
    • 在每个实例上,在构造时被复制一份
    • 在 Constructor.prototype 上,被所有实例共享
    • 在一个无法访问的内部作用域中
  3. this 而言,箭头函数有什么特别之处?
    • 它像方法一样,把 this 绑定到调用对象
    • 它没有自己的 this,而是捕获外层作用域的 this
    • 它总是把 this 绑定到 globalThis
    • 它禁止对 this 进行任何访问