原型模式

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

在应用程序中,我们经常需要创建许多相同类型的对象。 一个有用的方法是创建一个 ES6 类的多个实例。

假设我们想创造很多狗! 在我们的例子中,狗不能做很多事:它们只有一个名字,而且它们可以吠叫!


类 class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

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");

console.log(dog1.name); //Daisy
console.log(dog2.name); //Max
console.log(dog3.name); //Spot

原型 prototype

请注意,构造函数如何包含 name 属性,而类本身包含 bark 属性。
使用 ES6 类时,在类本身上定义的所有属性(在本例中为 bark)都会自动添加到原型prototype中。

可以通过访问构造函数上的 prototype 属性直接查看原型,或者通过任何实例上的 __proto__ 属性。

1
2
3
4
5
6

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

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

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

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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

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(); //Playing now! 

原型链 __proto__

原型链表示可能不止一个步骤。 的确! 到目前为止,我们只看到了如何访问在 __proto__ 引用的第一个对象上直接可用的属性。 然而,原型本身也有一个 __proto__ 对象!

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

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

  fly() {
    return "Flying!";
  }
}

让我们创造一只名叫“Daisy”的飞狗,让她吠叫飞翔!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

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(); //Woof! 
dog1.fly(); //Flying!

我们可以访问 bark 方法,因为我们扩展了 Dog 类。 SuperDog 原型上 proto 的值指向 Dog.prototype 对象!

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


Object.create

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

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

const pet1 = Object.create(dog);

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12

const dog = {
  bark() {
    console.log(`Woof!`);
  }
};

const pet1 = Object.create(dog);

pet1.bark(); // Woof!
console.log("pet1 上的原型prototype属性: ", Object.keys(pet1)); // pet1 上的直接原型prototype属性: []
console.log("pet1 原型链__proto__的属性: ", Object.keys(pet1.__proto__)); // pet1 原型链__proto__的属性:["bark"]

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


优点

原型模式允许我们轻松地让对象访问和继承其他对象的属性。

由于原型链允许我们访问未直接定义在对象本身上的属性,我们可以避免方法和属性的重复,从而减少使用的内存量。


知识点

  • prototype 对象/类本身的属性
  • __proto__ 原型链上的属性
  • new 将类实例化为一个对象
  • extends 类继承
  • Object.create 创建一个新对象