Prototype Pattern

Prototype pattern это удобный способ разделить свойства между многими объектами одного типа. Прототип это нативный объект JavaScript, он доступен объектам через обход по цепочке прототипов.

В наших приложениях нам часто необходимо создать множество объектов одного типа. С помощью ES6 это делается путем создания экземпляров ES6 класса.

Допустим, нам нужно создать много собак. В нашем примере собаки будут иметь имя – name и они смогут лаять – bark:

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 напрямую, обращаясь к prototype свойству конструктора, или к свойству __proto__ экземпляра класса.

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

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

Значение __proto__ любого экземпляра конструктора – это ссылка на прототип этого конструктора! Как только мы пытаемся обратиться к свойству объекта, которое не существует в этом объекте, JavaScript пройдет по цепочке прототипов в поиске данного свойства.

Prototype pattern имеет большой потенциал, когда нужно работать с объектами, имеющими доступ к одинаковым свойствам. Вместо создания дубликата свойства каждый раз, мы можем добавить свойство к прототипа, и все экземпляры получат к нему доступ через своем свойство __proto__.

Так как все экземпляры имеют доступ к прототипу, становится очень легко добавлять новые свойства, даже после того, как экземпляры созданы.

Допустим, наши собаки должны не только лаять, но уметь играть. Мы можем добавить метод 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();

Термин цепочка прототипов или ptototype chain указывает, что это не конечный этап. До сих пор мы увидели как получать доступ к свойствам объекта, напрямую доступного через свойство __proto__. Однако прототипы сами имеют объекты __proto__!

Давайте создадим другой тип собаки – супер собаку! Эта собака наследует поведение от обычной Dog, но еще получает способность летать. Мы можем создать SuperDog, расширяя класс Dog и добавим метод fly.

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

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

Давайте создадим летающую собаку Daisy, и позволим ей летать и лаять:

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

Мы имеем доступ к методу bark, потому мы расширили класс Dog. Значение __proto__ в прототипе SuperDog ссылается на объект Dog.prototype.

Становится понятно, почему это называется цепочкой прототипов: когда мы пытаемся получить доступ к свойству, которое отсутствует в объекте, JavaScript рекурсивно проходит вниз по цепочке объектов, находящихся в свойстве __proto__, пока не найдет это свойство.

Object.create

Метод Object.create позволяет создать объект, которому мы можем явно передать значение прототипа.

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

const pet1 = Object.create(dog);

Хотя объект pet1 сам не имеет ни каких свойств, он имеет доступ к свойствам по своей цепочке прототипов. Так как мы передали объект dog как прототип pet1, мы имеем доступ к методу bark

const dog = {
  bark() {
    console.log(`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__));

Отлично! Object.create – это простой способ позволить объектам наследовать свойства других объектов, назначая создаваемым объектам их прототип. Новый объект, таким образом, сможет получить доступ к новым свойствам, пройдя по цепочке прототипов.

С помощью паттерна Prototype можно легко получать доступ и наследовать свойства от других объектов. Поскольку цепочка прототипов позволяет нам обращаться к свойствам, которые не принадлежат объекту напрямую, мы можем избежать дублирования методов и свойств, что уменьшит потребление памяти.

  •  
  •  
  •  
  •  
  •  
  •