В JavaScript доступно несколько подходов к созданию объектов. В псевдоклассической модели мы используем функции для создания объектов.

Мы используем функции для создания объектов в псевдоклассической модели, но не используем фабричные функции. Псевдоклассическая модель (далее просто классическая) позволяет избежать недостатков фабричных функций:

  1. Мы можем узнать, откуда берется объект и, следовательно, какого рода это объект. Фабричные функции возвращают простые объекты, и это настолько специфично, насколько можно идентифицировать объекты.
  2. Мы избегаем использования большего объема памяти, чем необходимо, не привязывая общее поведение к каждому отдельному экземпляру объекта.

Обзор

В JavaScript объекты разделяют поведение не через типичное наследование на основе классов, а через ссылки, созданные прототипами. Когда мы пытаемся получить доступ к методу или свойству определенного объекта, этот метод не может быть определен непосредственно для этого объекта. В этом случае JS будет искать свойство или метод в «цепочке прототипов». То есть сначала он будет искать сам объект, а когда не сможет его найти, JavaScript будет искать прототип объекта. Этот прототип на самом деле является другим объектом и доступен через свойство «dunder-proto» (obj.__proto__). Это свойство __proto__ связывает два объекта вместе. После того, как не удалось найти свойство в вызывающем объекте, JS будет искать свойство в __proto__ связанном объекте, и если не найдет его там, он будет искать ЭТО object __proto__ связанный объект и так далее. JS продолжит цепочку прототипов, ища рассматриваемый метод или свойство, пока не найдет свойство или, наконец, не определит, что оно не существует в цепочке прототипов вызывающих объектов.

В классической модели мы создаем объекты и реализуем эту цепочку прототипов с помощью функций-конструкторов.

Чтобы создать «псевдокласс», мы определяем функцию-конструктор. Чтобы создать экземпляр объекта из этого класса, мы используем ключевое слово new.

новое ключевое слово

Когда мы используем ключевое слово new перед вызовом функции-конструктора, происходит несколько вещей:

  • Создается новый объект JavaScript
  • Новый объект устанавливается как контекст из `this`
  • Код внутри тела функции конструктора будет запущен с новым объектом в качестве контекста.
  • Если конструктор не возвращает объект, новый объект будет возвращен неявно.

Ниже мы определяем функцию-конструктор и создаем новый объект:

Итак, помня 4 шага, описанных выше:

  • вызов в строке 9 создаст новый объект
  • строка 5 устанавливает свойство нового объекта
  • возврата нет, поэтому новый объект неявно возвращается и присваивается переменной myCar.

Объект-прототип

Теперь функция-конструктор сама по себе является объектом, и когда мы его создаем, JS автоматически присваивает ему несколько свойств. Одним из таких свойств является свойство «прототип». prototype будет указывать на объект, который автоматически создается со свойством constructor, указывающим на функцию-конструктор. Этот объект-прототип будет __proto__ объектом по умолчанию для всех объектов, созданных с помощью этой функции-конструктора. Таким образом, создается первое звено в цепочке прототипов для вышеуказанного объекта myCar; myCar.__proto__ === Automobile.prototype. Следовательно, если мы хотим дать всем экземплярам автомобиля функцию startEngine, нам нужно определить ее в объекте Automobile.prototype:

Когда строка 11 выполняется, JS будет искать, но не найдет startEngine в объекте myCar, поэтому он будет искать myCar.__proto__, который равен Automobile.prototype, и найдет там startEngine. для этого будет установлено значение myCar, поэтому для текущего свойства, которое определено непосредственно в myCar, будет установлено значение true, и функция вернет текст аромата.

Собственность

Примечание о свойствах, определенных непосредственно в объектах. Иногда нам нужно знать, обращается ли JS к свойству напрямую из объекта или ему нужно искать цепочку прототипов. Мы можем получить список свойств, которые определены непосредственно для объекта через Object.getOwnPropertyNames(obj). Например, чтобы вернуть имена свойств, принадлежащих myCar:

//code omitted
Object.getOwnPropertyNames(myCar) // ["running"]

running является единственным свойством myCar и не имеет методов. Методы, которые мы будем вызывать для myCar, расположены в объекте-прототипе Automobile.

Подклассы

Мы создали псевдоклассы с функциями-конструкторами, и мы также можем создавать псевдо-подклассы с помощью этой модели:

Здесь мы создали конструктор Truck. Мы избегаем дублирования кода в строке 4, просто вызывая конструктор Automobile при установке нужного контекста. Итак, у нас есть новый объект myTruck, который не запущен. Давайте зажжем этого ребенка!

myTruck.startEngine();
Thrown:
TypeError: myTruck.startEngine is not a function

Мы не можем завести грузовик, потому что у него нет для этого способа. Чтобы исправить это, мы должны установить Truck как «наследуемый» от Automobile, на прототипе которого определен объект startEngine. Точнее, мы должны связать их вместе. Мы сделаем это, взаимодействуя со свойствами прототипа, о которых мы говорили ранее:

Строки 14 и 15 здесь наиболее важны.

В строке 14 мы получаем доступ к свойству прототипа конструктора Truck (тот, который указывает на объект, который становится __proto__ всех грузовиков) и устанавливаем для него значение Object.create(Automobile.prototype);.

Object.create(obj) создает новый объект JavaScript, у которого __proto__ равно аргументу obj.
Передавая в Automobile.prototype, мы устанавливаем свойство прототипа Truck как объект, __proto__ которого ссылается на Automobile.prototype, где мы определили метод startEngine. Таким образом, все грузовики теперь будут иметь доступ к этому методу через цепочку прототипов для автомобилей.

В строке 15 мы просто устанавливаем свойство конструктора Truck.prototype обратно в конструктор Truck. Когда мы выполнили строку 14, это свойство конструктора указывало на Automobile. Строка 15 позволяет нам сохранить ссылку конструктора на Truck.

Резюме

  • Мы хотим избежать копирования общих методов в каждый объект, они должны быть расположены на объектах-прототипах.
  • Мы делаем прототипное наследование в JavaScript, метод отправки вызывает цепочку прототипов.
  • В этой классической модели объекты создаются с использованием ключевого слова new.
  • Общее поведение принадлежит объекту-прототипу.
  • Подклассы имеют прототипы, связанные с прототипами суперкласса.