原型模式是在同一类型的许多对象之间共享属性的有用方法。原型是 JavaScript 原生的对象,可以通过原型链被对象访问。

在我们的应用程序中,我们经常需要创建许多相同类型的对象。想要达到此目的可以通过 ES6 类创建多个实例。

假设我们想创造很多狗,他们有一个名字,并且它们可以吠叫。

class Dog {
  constructor(name) {
    this.name = name;
  }

  bark() {
    return `Woof!`;
  }
}

const dog1 = new Dog("Daisy");
const dog2 = new Dog("Max");
const dog3 = new Dog("Spot");

请注意这里 constructor 如何包含 name 属性,并且类本身如何包含 bark 属性。使用 ES6 类时,类本身定义的所有属性(在本例中为 bark )都会自动添加到 prototype 中。

我们可以通过访问构造函数上的 prototype 属性或通过任何实例上的 __proto__ 属性直接查看 prototype

console.log(Dog.prototype);
// constructor: ƒ Dog(name, breed) bark: ƒ bark()

console.log(dog1.__proto__);
// constructor: ƒ Dog(name, breed) bark: ƒ bark()

构造函数的任何实例上的 __proto__ 值都是对构造函数原型的直接引用。每当我们尝试直接访问对象上不存在的对象属性时,JavaScript 就会沿着原型链向下查找该属性在原型链中是否可用。

当处理应该有权访问相同属性的对象时,原型模式非常强大。我们可以简单地将属性添加到原型中,而不是每次都创建属性的副本,因为所有实例都可以访问原型对象。

由于所有实例都可以访问原型,因此即使在创建实例之后也可以轻松向原型添加属性。

说我们的狗不仅应该会叫,还应该会玩,可以通过向原型添加 play 属性来实现这一点。

class Dog {
  constructor(name) {
    this.name = name;
  }

  bark() {
    return `Woof!`;
  }
}

const dog1 = new Dog("Daisy");
const dog2 = new Dog("Max");
const dog3 = new Dog("Spot");

Dog.prototype.play = () => console.log("Playing now!");

dog1.play();

原型链这一术语表明可以有多个步骤。到目前为止,我们只了解了如何访问 __proto__ 引用的第一个对象上直接可用的属性。然而,原型本身也有一个 __proto__ 对象。

让我们创造另一种狗,一只超级狗。这只狗应该继承普通 Dog 的一切,但它也应该能够飞。我们可以通过扩展 Dog 类并添加 fly 方法来创建超级狗。

class Dog {
  constructor(name) {
    this.name = name;
  }

  bark() {
    console.log("Woof!");
  }
}

class SuperDog extends Dog {
  constructor(name) {
    super(name);
  }

  fly() {
    console.log(`Flying!`);
  }
}

const dog1 = new SuperDog("Daisy");
dog1.bark();
dog1.fly();

当我们扩展 Dog 类时,我们可以访问 bark 方法。 SuperDog 原型上 __proto__ 的值指向 Dog.prototype 对象。

Flow

这也就能很清楚认识到为什么它被称为原型链:当我们尝试访问对象上不直接可用的属性时,JavaScript 会递归地遍历 __proto__ 指向的所有对象,直到找到该属性。

Object.create

Object.create 方法允许我们创建一个新对象,我们可以显式地将其原型值传递给该对象。

const dog = {
  bark() {
    return `Woof!`;
  },
};

const pet1 = Object.create(dog);
pet1.bark(); // Woof!
console.log("Direct properties on pet1: ", Object.keys(pet1));
console.log("Properties on pet1's prototype: ", Object.keys(pet1.__proto__));

虽然 pet1 本身没有任何属性,但它确实可以访问其原型链上的属性。由于我们将 dog 对象作为 pet1 的原型传递,因此我们可以访问 bark 属性。

Object.create 是一种简单的方法,通过指定新创建的对象的原型,让对象直接从其他对象继承属性。新对象可以通过沿着原型链向下访问新属性。

原型模式允许我们轻松地让对象访问并继承其他对象的属性。由于原型链允许我们访问对象本身没有直接定义的属性,因此我们可以避免方法和属性的重复,从而减少内存使用量。