В JavaScript доступно несколько подходов к созданию объектов. В псевдоклассической модели мы используем функции для создания объектов.
Мы используем функции для создания объектов в псевдоклассической модели, но не используем фабричные функции. Псевдоклассическая модель (далее просто классическая) позволяет избежать недостатков фабричных функций:
- Мы можем узнать, откуда берется объект и, следовательно, какого рода это объект. Фабричные функции возвращают простые объекты, и это настолько специфично, насколько можно идентифицировать объекты.
- Мы избегаем использования большего объема памяти, чем необходимо, не привязывая общее поведение к каждому отдельному экземпляру объекта.
Обзор
В 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
. - Общее поведение принадлежит объекту-прототипу.
- Подклассы имеют прототипы, связанные с прототипами суперкласса.