Что такое прототипное наследование в javascript

Прототипное наследование

В программировании мы часто хотим взять что-то и расширить.

Например, у нас есть объект user со своими свойствами и методами, и мы хотим создать объекты admin и guest как его слегка изменённые варианты. Мы хотели бы повторно использовать то, что есть у объекта user , не копировать/переопределять его методы, а просто создать новый объект на его основе.

Прототипное наследование — это возможность языка, которая помогает в этом.

[[Prototype]]

В JavaScript объекты имеют специальное скрытое свойство [[Prototype]] (так оно названо в спецификации), которое либо равно null , либо ссылается на другой объект. Этот объект называется «прототип»:

Прототип даёт нам немного «магии». Когда мы хотим прочитать свойство из object , а оно отсутствует, JavaScript автоматически берёт его из прототипа. В программировании такой механизм называется «прототипным наследованием». Многие интересные возможности языка и техники программирования основываются на нём.

Свойство [[Prototype]] является внутренним и скрытым, но есть много способов задать его.

Одним из них является использование __proto__ , например так:

Обратите внимание, что __proto__ — не то же самое, что [[Prototype]] . Это геттер/сеттер для него.

Он существует по историческим причинам, в современном языке его заменяют функции Object.getPrototypeOf/Object.setPrototypeOf , которые также получают/устанавливают прототип. Мы рассмотрим причины этого и сами функции позже.

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

Если мы ищем свойство в rabbit , а оно отсутствует, JavaScript автоматически берёт его из animal .

Здесь строка (*) устанавливает animal как прототип для rabbit .

Затем, когда alert пытается прочитать свойство rabbit.eats (**) , его нет в rabbit , поэтому JavaScript следует по ссылке [[Prototype]] и находит его в animal (смотрите снизу вверх):

Здесь мы можем сказать, что " animal является прототипом rabbit " или " rabbit прототипно наследует от animal ".

Так что если у animal много полезных свойств и методов, то они автоматически становятся доступными у rabbit . Такие свойства называются «унаследованными».

Если у нас есть метод в animal , он может быть вызван на rabbit :

Метод автоматически берётся из прототипа:

Цепочка прототипов может быть длиннее:

Есть только два ограничения:

  1. Ссылки не могут идти по кругу. JavaScript выдаст ошибку, если мы попытаемся назначить __proto__ по кругу.
  2. Значение __proto__ может быть объектом или null . Другие типы игнорируются.

Это вполне очевидно, но всё же: может быть только один [[Prototype]] . Объект не может наследоваться от двух других объектов.

Операция записи не использует прототип

Прототип используется только для чтения свойств.

Операции записи/удаления работают напрямую с объектом.

В приведённом ниже примере мы присваиваем rabbit собственный метод walk :

Теперь вызов rabbit.walk() находит метод непосредственно в объекте и выполняет его, не используя прототип:

Свойства-аксессоры – исключение, так как запись в него обрабатывается функцией-сеттером. То есть, это, фактически, вызов функции.

По этой причине admin.fullName работает корректно в приведённом ниже коде:

Здесь в строке (*) свойство admin.fullName имеет геттер в прототипе user , поэтому вызывается он. В строке (**) свойство также имеет сеттер в прототипе, который и будет вызван.

Значение «this»

В приведённом выше примере может возникнуть интересный вопрос: каково значение this внутри set fullName(value) ? Куда записаны свойства this.name и this.surname : в user или в admin ?

Ответ прост: прототипы никак не влияют на this .

Неважно, где находится метод: в объекте или его прототипе. При вызове метода this — всегда объект перед точкой.

Таким образом, вызов сеттера admin.fullName= в качестве this использует admin , а не user .

Это на самом деле очень важная деталь, потому что у нас может быть большой объект со множеством методов, от которого можно наследовать. Затем наследующие объекты могут вызывать его методы, но они будут изменять своё состояние, а не состояние объекта-родителя.

Например, здесь animal представляет собой «хранилище методов», и rabbit использует его.

Наследование в JavaScript: основные правила

Изучая наследование в JavaScript, автор (не специалист в JS) нашёл целесообразным зафиксировать как можно более системно, а значит строго и последовательно, основные правила этой «дисциплины».

Как известно, тема наследования в JS тесно связана с таким механизмом, как прототипы, сведения о которых, будучи незамысловаты, в то же время могут вызвать трудности с понимаем общей картины. Нередко на постижение материала уходит какое-то время, а ответы приходится искать на форумах.

После возвращения к конспектам решил выбрать публичный формат изложения, из-за чего были добавлены некоторые связующие пункты. В итоге получилась небольшая, не претендующая на академический характер статья, которая может быть полезна в качестве дополнительного материала для изучающих JavaScript.

Cube Dev , Удалённо , От 8000 $

Подразумевается, что читатель знаком с основами языка и вообще ООП.

Содержание:

1. Объект

Под объектом понимается тип данных, реализованных в виде набора свойств (полей и методов), имеющих имя и значение, а также экземпляр этого типа. Например, машина — это объект, и конкретный экземпляр, выпущенный на заводе, — тоже объект.

Ключевое слово this в методе объекта указывает на сам же объект и используется для обращения к его свойствам. Заметим, что свойства в js могут назначаться объекту не только при его создании, но и после. Также заметим, что возможно обращение к несуществующим полям, и они будут равны undefined , но обращение к несуществующим методам, конечно, невозможно.

2. Типы и классы

В js все объекты имеют тип object . Тем не менее, они могут иметь различную структуру, иначе говоря — состав свойств. Чтобы отличать «тип js» от «типа объекта» будем использовать для последнего обозначение «класс». Класс описывается через функцию, и такая функция называется функцией-конструктором, иногда просто конструктором. Имя функции-конструктора выступает именем класса, и соглашением предусмотрено, что оно начинается с прописной буквы. Экземпляры класса создаются через вызов new <ИмяФункции>() .

Вывод консоли показывает, что тип объекта object . Когда же мы выводим сам экземпляр класса, то обозначен он будет как Person <> . Также есть способ проверить, является ли объект экземпляром класса через оператор instanceof .

3. Класс Object

Для создания простого объекта, помимо литерального способа ( let o = <> ), использованного ранее, можно использовать функцию Object . Не следует путать тип object и класс Object . Последний выполняет роль фундаментального базового класса в генеалогии классов js. Если вывести Object в консоль, будет видно, что это функция.

4. Свойства функций и статические свойства класса

Говорят, что в js функция — это объект. Действительно, функции можно назначить произвольное свойство и вообще обращаться с ней как с обычным объектом, неважно, с маленькой она или большой буквы, является функцией-конструктором или возвращает результат вычисления.

Скажем, что функция в js имеет дуалистическую природу: с одной стороны, это функция, и её можно вызывать через круглые скобки, с другой, это объект, и у неё могут быть свои свойства, будь то поля или даже методы. Их объявление и обращение к ним выполняется через точку от имени функции. Нетрудно догадаться, что свойства функции-конструктора выступают статическими свойствами реализуемого ею класса. В этом примере мы объявляем у класса Person статическое поле count и статический метод getCount .

5. Флаги свойств объектов и некоторые статические методы функции Object

У свойств объекта есть значение и флаги. Их совокупность называется дескриптором свойства. Получить дескриптор можно через метод Object.getOwnPropertyDescriptor . Дескриптор в свою очередь представляет собой объект с полями с говорящими названиями value , writable , enumerable , configurable .

Метод Object.defineProperty позволяет назначить или модифицировать свойство объекта через дескриптор. В качестве дескриптора передаётся объект с любыми значимыми полями. В примере мы назначаем объекту свойство age со значением 18 и флагом enumerable , равным false . Этот флаг отвечает за видимость свойства при перечислении свойств объекта в некоторых конструкциях языка. Например, метод Object.keys проигнорирует свойство age . Однако есть метод Object.getOwnPropertyNames , который воспринимает и «неперечислимые» свойства.

Флаг writable отвечает за возможность перезаписи свойства, а configurable — его удаления (с помощью оператора delete ).

6. Прототипы

6.1. Связь объекта с прототипом

У объектов есть дефолтное свойство __proto__ , которое указывает на его «прототип». Это обычный объект: в том смысле, что это не функция. При создании объекта ему явно или неявно назначается прототип. При этом объект получает возможность обращаться к тем свойствам прототипа, которых у него нет.

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

6.2. Указание прототипа и обращение к свойствам прототипа

Рассмотрим существенно искусственный пример прототипной связи между двумя объектами.

Здесь мы создаём объект person , который далее используется в качестве прототипа при создании объекта с тривиальным именем person1 . Используется функция Object.create (это статический метод класса Object ), которая создаёт объект с указанным прототипом (справочно, по другой схеме также создаются объекты person2-4 ).

Несмотря на то, что у объекта person1 нет заданных свойств (мы намеренно их не указывали), он может использовать свойства своего прототипа. Когда мы вызываем метод speak , то сам метод и поле name заимствуются у прототипа. Затем мы назначаем объекту person1 свойство name , и теперь у него своё собственное значение этого свойства.

Когда мы вновь вызываем метод speak , сам метод будет взят у прототипа, а свойство name — уже у объекта person1 . Затем мы назначаем ему и свой собственный метод speak . В конце мы обращаемся к person.speak() , чтобы показать, что значение свойств name и speak у прототипа остались прежними.

6.3. Цепочка прототипов

Каждый объект имеет прототип. У прототипа объекта есть свой прототип, у того — свой и т.д. Цепочка заканчивается, если прототип становится равным null .

Когда происходит обращение к свойству объекта и такого свойства не обнаруживается, то поиск свойства происходит по цепочке прототипов.

В следующем листинге мы создаём цепочку объектов person , user , account . Чтобы назначить прототип уже созданному объекту, можно использовать функцию Object.setPrototypeOf , а чтобы получить прототип объекта — getPrototypeOf . Код выводит объект account с цепочкой прототипов.

6.4. Автоматические прототипы

В предыдущих примерах мы произвольно назначали одни объекты в качестве прототипов другим объектам. А что является прототипом объекта по умолчанию? Вообще есть ли он? Да, при создании объекта ему обязательно будет назначен прототип. До того было сказано, что прототип может быть назначен неявно.

После описания функции машина сама («под капотом») назначит ему особенное свойство prototype (то есть статическое свойство класса), заполнит его, а при создании объектов с помощью этой функции присвоит значение этого свойства prototype их (создаваемых объектов) свойству __proto__ .

Например, мы описали функцию-конструктор Person (неважно, что внутри тела функции). Тогда, незаметно, произойдёт вот что.

A. Функции будет назначено свойство prototype типа object .

Условно это можно записать так: Person.prototype = <> ;

B. Объекту prototype будет назначено свойство constructor со значением ссылки на саму функцию. Условно это можно записать так:

C. Объекту prototype будет назначено свойство __proto__ со значением ссылки на Object.prototype .

Условно это можно записать так:

Разумеется, это условный код, призванный раскрыть немного смысла внутренней реализации.

Объект Object.prototype также имеет свойства constructor , равный функции Object , и __proto__ , равный null .

Благодаря свойству constructor свойства prototype функции (в данном случае Person.prototype.constructor ) можно создавать объекты через обращение к прототипу другого объекта. У person1 нет свойства constructor , поэтому при вызове этого метода машина начнёт поиск в person1.__proto __. Свойство __proto__ , как мы уже знаем, указывает на Person.prototype , а у последнего как раз есть свойство constructor , которое, в свою очередь, ссылается на саму функцию Person , и, таким образом, person1.constructor есть Person . Витиевато, но правда.

Заканчивает листинг создание объекта с помощью constructor’а строкового литерала (в данном случае пустой строки). Обратите внимание, что во всех случаях используется оператор new .

6.5. Расширение прототипов

Вследствие того, что при создании нового объекта ему назначается прототип равный свойству prototype функции-конструктора, все прототипы новых объектов равны друг другу и ссылаются на один и тот же объект. Отсюда следует, что если добавить в prototype новое свойство, то оно будет доступно всем объектам класса.

Здесь у каждого объекта наличествуют собственные свойства name и speak , а вот свойство talk принадлежит одному лишь прототипу. Объекты могут беспрепятственно обращаться к свойству прототипа. При этом указатель this не теряет свой контекст и ссылается на сам объект. Отсюда следует, что при проектировании классов через функции-конструкторы целесообразно размещать методы класса как свойства prototype .

6.6. Как работает оператор instanceof

Оператор имеет синтаксис obj instanceof <ИмяФункции> , где слева какой-то объект, а справа какая-то функция-конструктор. Это выражение возвращает истину, если объект является экземпляром функции.

Объект считается экземпляром функции, если свойство prototype функции, т.е. <ИмяФункции>.prototype , является звеном цепочки прототипов объекта. Т.е., если:

6.7. О прочтении слов «прототип» и «prototype»

Говоря о прототипе объекта, мы имеем в виду некий иной объект, на который указывает свойство __proto__ этого первого объекта. В то же время, как правило, все объекты одного класса имеют один прототип, на который указывает свойство prototype функции-конструктора. Поскольку функция сама является объектом, выражение «прототип функции» может быть воспринято двояко: то ли это <ИмяФункции>.__proto__ , то ли <ИмяФункции>.prototype . В целях этой статьи под прототипом функции мы понимаем <ИмяФункции>.__proto__ , а свойство prototype , равное свойству __proto__ экземпляров этой функции, так и называем — prototype .

7. Прототипная связь функций

Если функция является объектом, то, стало быть, у неё может/должен быть прототип. Если мы возьмём любую функцию, то вывод в консоль выражения типа <ИмяФункции>.__proto__ покажет результат. Более того, они все ссылаются на один и тот же объект.

Этот загадочный объект находится по адресу Function.prototype . Здесь Function тоже функция. То есть получается, что при описании/создании какой-нибудь функции она как будто бы создаётся через вызов new Function() . В принципе, мы даже можем создать функцию подобным образом.

Из этого, конечно, не следует, что именно так и создаются функции. Просто их свойство __proto__ указывает на Function.prototype .

Возникает резонный вопрос: если прототип всякой функции ссылается на Function.prototype , то что является прототипом самой функции Function ? Ответ: Function.__proto__ также ссылается на Function.prototype . В принципе это логично, ибо Function и сама является функцией.

А вот Function.prototype.__proto__ ссылается на Object.prototype , что тоже логично, ибо свойство prototype любой функции является объектом, а не функцией. Вспомним как работает оператор instanceof и изобразим прототипные отношения наших функций следующим образом.

8. Полезные свойства Function.prototype

Если вывести в консоль объект Function.prototype , можно заметить, что у него есть какие-то свойства. Нас интересует метод call .

Рассмотрим следующий пример. Пусть у нас есть функция-конструктор Person . Мы создали объект этого класса (person1) стандартным образом через оператор new . Теперь предположим, мы хотим превратить произвольный объект в экземпляр класса Person . Что для этого нужно сделать?

Во-первых, связать объект через прототип. В случае объекта person2 мы назначаем свойству __proto__ значение Person.prototype через метод Object.setPrototypeOf , а в случае person3 создаём его сразу с указанным прототипом.

Во-вторых, нам нужно вызвать функцию Person применительно к объекту так, чтобы объект ( person2 и person3 ) стал this . Это возможно сделать с помощью метода call Function.prototype .

Поскольку Person.__proto__ = Function.prototype , мы можем писать просто Person.call . Первым аргументом мы передаём объект, который внутри Person выступит в качестве this (иначе говоря, мы передаём контекст), остальные аргументы call это аргументы функции Person (в данном случае один аргумент name ). Выводим объекты person1-3 в консоль, и убеждаемся, что они однотипны.

Таким хитрым образом мы смогли имитировать создание экземпляра функции Person без оператора new . Подобный приём нам пригодится при реализации наследования.

9. Наследование с помощью прототипов

Рассмотрим класс TestItem , представляющий собой вопрос проверочного теста. У него есть поля question (текст вопроса), points (ответы) и answer (номер правильного ответа) и метод check , проверяющий ответ. Предположим, параметр answer — это номер выбранного ответа, метод check просто сравнивает его с правильным. В листинге мы создаём вопрос и имитируем его проверку.

Всё хорошо, но в один момент выяснилось, что правильных ответов может быть несколько, и к тому же возникла необходимость добавить подсказку. Принято решение расширить класс в новый: MultipleChoiseTestItem . Порядок действий при расширении класса следующий.

В теле функции-конструктора производного класса при необходимости вызывается конструктор базового класса с помощью метода call и передачей текущего this с возможными прочими аргументами. В нашем случае вызов необходим, ибо конструктор базового класса «что-то делает», а именно заполняет поля this аргументами question , points и answer .

Поле hint мы заполняем уже в теле конструктора производного класса.

В обязательном порядке устанавливается прототипная связь между prototype производного и базового класса. То есть свойство __proto__ свойства prototype производного класса должно ссылаться на prototype базового класса. В нашем случае MultipleChoiseTestItem.prototype.__proto__ устанавливается равным TestItem.prototype .

Также устанавливается прототипная связь между самим производным и базовым классом. В нашем случае MultipleChoiseTestItem.__proto__ устанавливается равным TestItem .

В листинге мы используем для связи метод Object.setPrototypeOf .

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

На практике некоторые приёмы обращения со свойством __proto__ считаются нерекомендованными. Например, при наследовании вместо метода Object.setPrototypeOf используется Object.create . В этом случае prototype функции-конструктора создаётся с нуля (якобы быстрее создать prototype с нуля, чем модифицировать существующий).

После такого действия объекту prototype необходимо назначить свойство constructor , равным самой функции-конструктору. В следующем примере для этого используется метод Object.defineProperty с передачей дескриптора свойства. Если мы напишем просто User.prototype.constructor = User (в принципе можно и так), то флаги свойства могут быть отличны от дефолтных значений флагов свойства constructor .

Отдельная история наблюдается с прототипной связью между функцией-конструктором производного и базового классов. Здесь мы не можем пересоздать функцию. Статические свойства базового класса можно скопировать в базовый с помощью метода Object.assign . Однако прототипной связи в данном случае установлено не будет, User.__proto__ будет ссылаться на Function.prototype , а не на Person .

Можно сказать, что статья даёт простор для выбора того или иного способа реализации базовых методов в производном классе с учётом наилучших практик и рекомендаций, которые можно найти в сети, ограничиваясь их упоминанием. Впрочем, js предоставляет и классический способ проектирования классов.

10. Class вместо function

В js поддерживается такая языковая конструкция как класс. Перепишем наш пример с их использованием.

Реализация классов с помощью конструкции class оставляет за бортом возню с прототипами. Не будем заостряться на синтаксисе, ибо он не из ряда вон выходящий, но обратим внимание на то, что прототипная архитектура функций осталась прежней. Сам класс при этом остаётся по сути и за исключением каких-то нюансов той же функцией.

Заметим, что прототип производного класса равен базовому: MultipleChoiseTestItem.__proto__ = TestItem . При использовании конструкции function такая связь устанавливалась через метод Object.setPrototypeOf . Эта связь позволяет производному классу вызывать статические методы базового класса.

11. Наследование статических свойств

Как и обычные свойства, статические также наследуются, что показано в предыдущем пункте на примере метода printTest .

В отличие от обычных методов, указатель this в теле статического метода содержит ссылку на саму функцию, а не экземпляр класса.

Обращение к статическому свойству выполняется либо через имя функции, либо через this в теле статического метода.

Операция присваивания свойств производного класса, которая создаёт одноимённое свойство, независимое от базового (например, переопределение метода), может приобрести разночтение относительно статического поля.

Рассмотрим следующий показательный пример.

При вызове метода resetCount , поскольку собственный метод у класса User не реализован (то есть не переопределён), вызывается метод базового класса. Однако this указывает на класс User , что, собственно, правильно, ибо метод вызван объектом (в данном случае это функция) User . В теле метода this.count = 0 превращается в User.count = 0, что приводит к созданию у класса User своего статического поля count . Поле count базового класса обнулено не будет.

Перепишем пример на классах и размножим поле count на два поля. К одному из них в теле статического метода будем обращаться через this.count1 , а к другому через прямое обращение к базовому классу как Person.count2 . В последнем случае как раз-таки будет обнулено собственное свойство count2 класса Person , класс User получит собственное свойство count1 (равное 0), а User.count2 вернёт значение поля базового класса.

Итого

В этой статье мы постарались раскрыть ключевые моменты наследования в js, особое внимание уделив прототипной архитектуре. Скажем само собой разумеющееся: некоторые (или все) темы, затронутые в статье, требуют отдельного изучения, и многое здесь не упомянуто.

С развитием классов в js нюансы работы с прототипами имеют меньшее распространение, но сохраняют свой шарм, присущий JavaScript в целом.

Прототипное наследование Javascript (+ видео с примером)

В этом видео мы разберемся — что такое прототипы и как работает прототипное наследование в JS.

Давайте рассмотрим небольшой пример:

В JS есть нативный или встроенный объект под названием Date, который используется для работы с датами и временем.

Чтобы воспользоваться функционалом объекта Date — нам сначала нужно создать наш собственный экземпляр этого объекта. Для этого мы используем специально слово new:

В переменной myDate мы получаем новый экземпляр объекта Date, который содержит текущий момент времени. Далее, мы можем использовать различные встроенные методы объекта Date, обращаясь к нашему экземпляру.

Например, мы можем получить только текущий год:

или текущее время:

То есть теперь, для нашего экземпляра объекта Date (переменная myDate) — доступно много различных методов, которые живут в головном объекте Date.

То же самое касается других нативных объектов, таких как Object, Number, Array и так далее.

Если мы напишем название любого из этих объектов в консоли и нажмемenter, мы увидим, что все они являются функциями.

Если запускать эти функции, используя слово new, то в ответ мы будем получать объекты (экземпляры головного объекта). Давайте это проверим:

Создаем собственный головной объект

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

Для этого мы будем использовать так называемую функцию конструктор . Название таких функций принято писать с большой буквы.

Создаем экземпляры головного объекта

Теперь мы можем использовать эту функцию конструктор для создания отдельных экземпляров машин.

Таким образом, мы получили экземпляр нашего головного объекта Auto — пустой объект, присвоенный нашей переменной tesla. Давайте используем метод constructor, чтобы удостовериться, что это действительно так:

Добавляем свойства к экземплярам

Теперь, давайте добавим какие-то свойства в каждый из наших экземпляров головного объекта Auto. Для этого мы используем нашу функцию конструктор:

Внутри функции Auto, значение this относится к каждому конкретному экземпляру, который мы будем создавать:

Мы создали 2 экземпляра нашего головного объекта Auto. Каждый экземпляр обладает своими собственными свойствами.

Добавляем методы к экземплярам

Теперь давайте попробуем добавить какие-то методы к нашим машинкам. Метод drive будет расходовать бензин каждой машины (свойство gas):

Все отлично работает — но есть небольшой ньюанс!

Каждый раз, cоздавая новый экземпляр машины, мы также создаем новую функцию drive (для каждого экземпляра)!

Как в этом убедиться?

Давайте сравним функцию drive у разных экземпляров машин:

Так что же в этом плохого?

В текущем примере, где у нас всего 2 экземпляра машин — проблем никаких нет. Но если количество экземпляров возрастет до 1,000 или десятков тысяч, это окажет негативное влияние на производительность.

Используем прототип (js prototype)

Чтобы не создавать каждый раз новый метод drive() для каждого экземпляра машины, мы можем поместить этот метод в, так называемый, прототип (prototype) нашего головного объекта.

Таким образом мы создадим всего один метод drive(), который будет использоваться всеми экземплярами наших машин.

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

Теперь давайте посмотрим на экземпляр нашего объекта в переменной nissan:

Мы видим, что метод drive пропал из списка свойств. Это происходит потому, что теперь метод drive "живет" в прототипе объекта.

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

Давайте убедимся в этом на примере:

Давайте добавим еще одно свойство в прототип нашего объекта:

Если мы в консоли напишем:

Теперь давайте дополнительно добавим свойство discount в наши экземпляры:

Если мы повторно в консоли проверим свойство discount конретного экземпляра, то получим значение "70%":

То есть, первым делом идет поиск в свойствах экземпляра. Если свойство отсутствует — JS делает поиск в прототипе!

Обновляем логику работы метода в прототипе для всех экземпляров

Часто требуется обновить логику работу какого-либо метода. Используя прототипное наследование, обновленный метод становится доступным для всех экземпляров!

Рассмотрим пример

Создаем метод info и ниже меняем его, в соответствии с новыми требованиями:

На этом этапе, для всех экземпляров нашего объекта Auto доступна обновленная версия метода info.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *