Прототипное наследование в JS
Сначала я думал, что разобрался с прототипным наследованием в JS (ведь оно такое простое), а теперь мне кажется, что я не понимаю, зачем все это нужно. Чаще всего наследование и создание подклассов нужно для того, чтобы использовать полиморфизм. Для этого используются ссылки или указатели на базовый класс, связанные с объектами производных классов. Это позволяет обрабатывать разные данные единообразно. Но зачем в JS люди пихают в прототип методы, которые можно определить в самом классе? Ведь здесь нет полиморфизма. В чем профит? Как такое наследование в JS выглядит глобально, какие задачи решает, для чего оно?
Отслеживать
задан 13 мая 2018 в 13:22
2,517 3 3 золотых знака 31 31 серебряный знак 49 49 бронзовых знаков
Можно с примерами? Не очень понятно что значит «пихать метод в прототип» и «определять в самом классе»
13 мая 2018 в 14:02
@Alexey Ten, ну вот хотя бы вот это github.com/tastejs/todomvc/blob/gh-pages/examples/vanillajs/js/… Методы, специфичные для модели, запихали зачем-то в прототип. В классическом ООП в базовый класс выносятся общие методы. В данном же примере происходит какое-то странное колдунство вместо ООП
Когда использовать методы в конструкторе, а когда — в прототипе?
Я не понимаю, в каких случаях необходимо метод объекта объявлять в самом конструкторе, а когда — в прототипе.
Вот например в коде ниже я не вижу разницы между двумя методами и хотелось бы чтобы кто-то пролил свет на их использование.
function A() < this.method = function() < alert('Метод конструктора'); >> A.prototype.method2 = function() < alert('Метод прототипа'); >let object = new A();
Известно ведь, что в новом стандарте ES6 с синтаксисом классов и вовсе нет методов в конструкторе.
- Вопрос задан более трёх лет назад
- 1216 просмотров
2 комментария
Простой 2 комментария

«в новом стандарте ES6 с синтаксисом классов и вовсе нет методов в конструкторе» — конечно есть.
«я не вижу разницы между двумя методами» — разница в том, что один объявлен как свойство экземпляра, а другой — как свойство прототипа, методы из которого наследуются экземплярами. Технически, это исчерпывающий список различий. Остальное — последствия, например хранение метода в каждом экземпляре приведет к увеличенному расходу памяти и т.д.
«Когда использовать методы в конструкторе, а когда — в прототипе?» — и в том и в другом случае ответ один: когда это нужно. Если не видите нужды в чем-то, то не делайте этого; все просто. Нужно забить память объявлением однотипных функций? Объявляйте методы в конструкторе. Не нужно? Объявляйте в прототипе.
Хорошего примера объявления методов в конструкторе, к сожалению, не могу привести.
Введение
Как вы, должно быть, знаете, в JavaScript используется особенная модель программирования. Оставим философские размышления о правильности или неправильности подобного подхода за рамками данного доклада и попытаемся понять, как с таким подходом жить. Тем более, что выбора у нас особо и нет.
Итак, обо всем по порядку.
Объекты
Объекты в JavaScript представляют собой, в первую очередь, ассоциативный массив. В нем строковым или числовым (а при большом желании и типа boolean) ключам соответствует одно любое значение. Значением массива может быть и функция, в таком случае она является методом этого объекта.
Для наглядности я буду обозначать объекты таким вот образом:

Кстати, многие знают, что у обыкновенных массивов и объектов практически никаких различий, кроме набора доступных методов и небольшого количества синтаксического сахара. Например, нет никакой разницы между obj.property и obj[‘property’] и следующий код:
var obj = <>; var arr = [];
генерирует идентичные объекты 1 . Единственной разница — в указателе на прототип. Кстати, о прототипах.
Прототипы
Прототип примерно соответствует классу в стандартной модели. Особо обратите внимание на слово «примерно», потому что эти понятия не тождественны: есть множество различий.
Прототип — это то место, где будет искаться поле или метод в том случае, если его нет в самом объекте. Например, когда мы пишем a.method() , то сначала наличие метода method будет проверено в объекте a , и, если там его нет, то в прототипе объекта a . Каждый объект имеет свой прототип.

«[[прототип]]» на рисунке выше — это внутреннее свойство объекта, его нельзя прочитать напрямую. Но есть стандартная функция Object.getPrototypeOf(obj) 2 , которая позволяет посмотреть, что именно находится в прототипе.
Но по своей сути прототип тоже является объектом, ассоциативным массивом. А это значит, что у него тоже есть свой прототип. Таким образом строятся цепочки наследования. Например, если мы напишем arr = [] , то у нас будет сформирована вот такая структура:

И даже у Object есть прототип, но он равен null , а потому никуда не ссылается.
Конструктор
Идем дальше. Как вы, должно быть, знаете из ООП, конструктор — это специальная функция, которая вызывается при создании объекта. В JavaScript тоже есть такая функция. Вернее даже так: в JavaScript любая функция может исполнять роль конструктора. Я понимаю, это сложно сразу воспринять, особенно учитывая тот факт, что функции зачастую возвращают какие-то данные с помощью конструкции return . Так вот, если вы воспользуетесь подобной функцией как конструктором, возвращаемое значение будет успешно отброшено и конструктор вернёт сконструированный объект 3 .
Для того чтобы вызвать функцию как конструктор, достаточно написать перед ней слово new :
var f = new F();
Необходимо упомянуть ещё об одной детали перед тем, как мы перейдём к связи конструкторов и прототипов. Любая функция может вести себя как объект. Вы же помните, что в JavaScript всё (почти) может вести себя как объект? Т. е. ничто не мешает нам написать 4 :
F.someValue = 1; F.someMethod = function () <>;
У объекта функции есть несколько стандартных свойств/методов. Одно из них называется prototype . Это свойство и является будущим прототипом нашего объекта, сконструированного из этой функции.

в момент создания объекта внутренняя ссылка этого объекта на прототип начинает ссылаться на тот же объект, что и свойство prototype у его конструктора. Важно понимать, что у объектов нет свойства prototype , это свойство есть только у функций. И оно равно пустому объекту <> , а это то же самое, что и new Object() .
Пример создания объекта
var F = function () < this.a = 1; this.b = 2; >F.prototype.a = 3; F.prototype.c = 4; var f = new F(); f.a = 5; f.c = 6;
Разберём происходящее в этом примере более подробно.
К моменту создания объекта в строке 7 мы приходим с такой структурой:

Затем происходит вызов конструктора и создаётся новый пустой объект с прототипом, взятым из функции F :

Вслед за этим наш свежесозданный объект заполняется свойствами в конструкторе F .

И уже затем конструктор возвращает объект. Дальше он присваивается переменной f и дозаполняется.

Свойство __proto__
Помните, я сказал, что значение прототипа нельзя получить напрямую? Я несколько слукавил. Большая часть браузеров (кроме Internet Explorer) предоставляют подобную возможность. Для этого у каждого объекта есть свойство __proto__ .

Более того, его можно не только читать, но и писать. И эта его особенность пригодится нам в упрощённой схеме наследования. Как именно, я расскажу чуть позже.
Некоторые считают свойство __proto__ устаревшим (deprecated), но это не так. Оно скорее нестандартное, но и это будет в относительно скором времени исправлено. В разрабатываемой спецификации ECMAScript 6 оно уже описано. Если вдруг кто не знает, JavaScript — это один из диалектов ECMAScript (наряду с ActionScript), а потому должен подчиняться общей спецификации.
Наследование
Теперь, когда основные концепции разобраны, пора переходить к наследованию. Начнём с неправильных вариантов, которые зачастую выдаются за верные.
Вариант №1
Простое копирование прототипов.
var ParentClass = function () <>; ParentClass.prototype.method1 = function () <>; var ChildClass = function () <>; ChildClass.prototype = ParentClass.prototype; ChildClass.prototype.method2 = function () <>;
Вы, наверное, уже и сами видите, почему этот вариант неправильный. Но всё равно давайте разберём.
В строке 1 объявляем конструктор родителя:

Дальше добавляем в прототип ParentClass метод method1 :

Строка 3. Объявляем дочерний конструктор:

Присваиваем дочернему прототипу родительский:

«Добавляем» ещё один метод к дочернему прототипу:

Как видите, получилось совсем не то, что ожидалось.
Вариант 2
Немного лучше первого хотя бы тем, что работает. Однажды меня пытались убедить, что именно так и должно выглядеть наследование.
var ParentClass = function () <>; // ParentClass contructor ParentClass.prototype.method1 = function () <>; var ChildClass = function () <>; // ChildClass constructor ChildClass.prototype = ParentClass.prototype; var child = new ChildClass(); child.method2 = function () <>;
Различие только в двух последних строчках. Мы создаём экземпляр класса и добавляем в уже созданный объект ещё один метод.

Но ведь в таком случае нужно каждый раз писать включение дополнительных методов в объект! Согласен, можно вынести инициализацию в конструктор, ведь именно для этого он и предназначен.
var ChildClass = function () < // ChildClass constructor this.method2 = function () <>; >;
Большая (и не всегда очевидная) проблема с этим методом — количество используемой памяти. Чуть позже я покажу результаты тестирования, они играют не в пользу данного метода. Ещё одна проблема в том, что наследование от ChildClass неосуществимо прямыми методами, придётся самостоятельно вызывать конструктор родителя, чтобы он добавил недостающие методы.
Вариант 3
В этом варианте будем простым копированием добавлять необходимые поля и методы в прототип:
var ParentClass = function () <>; // ParentClass contructor ParentClass.prototype.method1 = function () <>; var ChildClass = function () <>; // ChildClass constructor for (var prop in ParentClass.prototype) < if (ParentClass.prototype.hasOwnProperty(prop)) < ChildClass.prototype[prop] = ParentClass.prototype[prop]; >> ChildClass.prototype.method2 = function () <>;
Тут стоит обратить внимание на условие ParentClass.prototype.hasOwnProperty(prop) . Эта конструкция for..in перебирает не только то, что находится в самом объекте, но ещё и то, что находится в его прототипе (строка [[прототип]] на диаграммах выше). Поэтому получается, что будут скопированы и те методы, которые достались по наследству от класса Object . А зачем нам их копировать, если они и так будут у дочернего класса? Метод hasOwnProperty как раз и позволяет избежать подобного поведения. Это стандартный метод класса Object и он возвращает true , если свойство находится в самом объекте, а не в его прототипе.
В результате выполнения кода выше мы получим 2 класса, как и задумывали:

Однако в таком способе, помимо необходимости вручную копировать поля и методы, есть и ещё один недостаток: невозможно использовать оператор instanceof . если в любом языке childObject instanceof ParentClass вернёт true , то здесь это не так. Кстати, то же самое касается и предыдущего варианта.
Вариант 4. Правильный
Что же, самое время разобраться с правильным вариантом. Как вообще должна выглядеть правильная структура объекта? На мой взгляд, так:

Так как же получить подобную красоту? Начнём с того, что у нас есть 2 конструктора: ChildClass и ParentClass .

Требуется, чтобы ChildClass.prototype был равен пустому объекту, у которого в прототипе будет стоять ParentClass.prototype .

Чтобы получить такой результат, достаточно одной строки:
ChildClass.prototype = new ParentClass();
Хотя в этом случае будет выполнен и конструктор ParentClass , что не всегда желательно. Поэтому лучшим решением будет создать временный класс с таким же прототипом, как в ParentClass , но пустым конструктором. И уже экземпляр этого временного конструктора присваивать дочернему прототипу:
var tempCtor = function () <>; tempCtor.prototype = ParentClass.prototype; ChildClass.prototype = new tempCtor();
Вот в принципе и всё. То, что многие разработчики считают архисложным, записывается в строчках, которые, сути, можно заменить вообще одной.
Вариант 5. Используем Object.create
Идем дальше. Вы же помните, что в JavaScript есть несколько способов сделать одно и то же? Тоже и с наследованием. Всё шаманство с созданием временного класса и последующим созданием объекта можно выразить с помощью вызова всего лишь одного метода — Object.create .
Посмотрим описание этого метода. Он принимает 2 параметра: прототип объекта и описание свойств. Свойства выходят за рамки этой статьи, можете посмотреть их сами, а вот первый параметр — это именно то что нужно. Получается, что если вызвать Object.create только с одним параметром (прототипом создаваемого объекта), то результат будет аналогичным предыдущему способу.
ChildClass.prototype = Object.create(ParentClass.prototype);
К сожалению, Object.create объявлен только в спецификации ECMAScript 5, поэтому данный способ наследования работает только в относительно новых браузерах (IE9+, FF4+, O12+).
Вариант 6. Магия с prototype.__proto__
Ну и напоследок, есть совсем простой способ получить то, что требуется. Я рассказывал про наличие в некоторых браузерах свойства __proto__ , которое представляет собой ссылку на прототип и которое в остальных браузерах недоступно. Получается, что мы можем просто указать прототипу класса, от кого он унаследован, безо всяких дополнительных действий.
ChildClass.prototype.__proto__ = ParentClass.prototype;
Приведу ещё раз для наглядности схему с тем, что делает эта инструкция:

Вот так, легко и просто. Но посмотрим на список браузеров, где подобная конструкция будет работать правильно: Chrome, Firefox, Safari >= 5, Opera >= 10.50. Ни одна из версий Internet Explorer сюда не входит.
Упомяну так же, что данный способ очень часто используется в node.js, в этом случае нет необходимости поддерживать целый зоопарк различных браузеров.
Сравнение различных вариантов
Итак, у нас получилось 3 рабочих варианта, 2 и один совсем не рабочий. Неплохо бы их сравнить.
Поддержка браузерами
| Google Chrome | Mozilla Firefox | Opera | IE6 | IE7 | IE8 | IE9 | IE10 |
|---|---|---|---|---|---|---|---|
| Добавление методов в конструкторе | |||||||
| Копирование прототипа через for..in | |||||||
| Временный конструктор | |||||||
| Object.create | (4+) | (12+) | |||||
| __proto__ |
Производительность
| Память (Мб) | Скорость определения (оп/с) | Скорость создания (оп/c) | |
|---|---|---|---|
| Добавление методов в конструкторе | 74.91 | 12 152 481 (34375,7%) | 31 716 (0,2%) |
| Копирование прототипа через for..in | 5.61 | 7 946 (22,5%) | 13 880 306 (96,7%) |
| Временный конструктор | 5.03 | 20 484 (57,9%) | 14 361 306 (100%) |
| Object.create | 4.98 | 35 352 (100%) | 14 103 497 (98,2%) |
| __proto__ | 4.98 | 22 474 (63,6%) | 13 893 309 (96,7%) |
Как видно, первый способ можно смело исключить ужасно медленного создания объектов. Не смотрите, что у него скорость определения классов в 35000 раз выше, это разовая операция, а вот создание будет использоваться постоянно.
Второй способ уступает по скорости определения, но всё ещё вполне годен для использования, если не принимать в расчёт тот факт, что это не совсем наследование. На самом деле, он активно используется, когда вопрос заходит об аналоге mixin или traits.
Остальные способы примерно равны по производительности и потреблению памяти.
Использование памяти измерялось в Google Chrome. В нем есть инструмент Profiles, который позволяет создавать снимки памяти и изучать, на что она была потрачена. Вы можете и сами провести исследование и сравнить с моим. Вот для этого вам несколько ссылок: генератор страниц для проверки потребления памяти, тестирование скорости определения классов на jsperf и тестирование скорости создания объектов на jsperf.
Лично я продолжу применять способ с использованием временного конструктора того, что он поддерживается всеми браузерами.
- На самом деле, при создании массива будет еще создано поле length , в котором содержится длина.
- Поддерживается начиная с IE9.
- Это не верно, если возвращает объект. В этом случае результатом будет возвращённый объект, а не полученный из этого конструктора. в этом случае нет большой разницы между вызовом функции с new и вызовом без new .
- Кстати, именно так реализуются статические методы и свойства.
Прототипы объектов
JavaScript часто описывают как язык прототипного наследования — каждый объект, имеет объект-прототип, который выступает как шаблон, от которого объект наследует методы и свойства. Объект-прототип так же может иметь свой прототип и наследовать его свойства и методы и так далее. Это часто называется цепочкой прототипов и объясняет почему одним объектам доступны свойства и методы которые определены в других объектах.
Точнее, свойства и методы определяются в свойстве prototype функции-конструктора объектов, а не в самих объектах.
В JavaScript создаётся связь между экземпляром объекта и его прототипом (свойство __proto__ , которое является производным от свойства prototype конструктора), а свойства и методы обнаруживаются при переходе по цепочке прототипов.
Примечание: Важно понимать, что существует различие между прототипом объекта (который доступен через Object.getPrototypeOf(obj) или через устаревшее свойство __proto__ ) и свойством prototype в функциях-конструкторах. Первое свойство является свойством каждого экземпляра, а второе — свойством конструктора. То есть Object.getPrototypeOf(new Foobar()) относится к тому же объекту, что и Foobar.prototype .
Давайте посмотрим на пример, чтобы стало понятнее.
Понимание прототипа объектов
Вернёмся к примеру, когда мы закончили писать наш конструктор Person() — загрузите пример в свой браузер. Если у вас ещё нет работы от последней статьи, используйте наш пример oojs-class-further-exercises.html (см. Также исходный код).
В этом примере мы определили конструктору функцию, например:
function Person(first, last, age, gender, interests) // Определения методов и свойств this.name = first: first, last: last, >; this.age = age; this.gender = gender; //. см. Введение в объекты для полного определения >
Затем мы создаём экземпляр объекта следующим образом:
var person1 = new Person("Bob", "Smith", 32, "male", ["music", "skiing"]);
Если вы наберёте « person1. » в вашей консоли JavaScript, вы должны увидеть, что браузер пытается автоматически заполнить это с именами участников, доступных на этом объекте:

В этом списке вы увидите элементы, определённые в конструкторе person 1 — Person() — name , age , gender , interests , bio , и greeting . Однако вы также увидите некоторые другие элементы — watch , valueOf и т. д. — они определены в объекте прототипа Person (), который является Object .

Итак, что произойдёт, если вы вызываете метод в person1 , который фактически определён в Object ? Например:
.valueOf();
Этот метод — Object.valueOf() наследуется person1 , потому что его конструктором является Person() , а прототипом Person() является Object() . valueOf() возвращает значение вызываемого объекта — попробуйте и убедитесь! В этом случае происходит следующее:
- Сначала браузер проверяет, имеет ли объект person1 доступный в нем метод valueOf() , как определено в его конструкторе Person() .
- Это не так, поэтому следующим шагом браузер проверяет, имеет ли прототип объекта ( Object() ) конструктора Person() доступный в нем метод valueOf() . Так оно и есть, поэтому он вызывается, и все хорошо!
Примечание: Мы хотим повторить, что методы и свойства не копируются из одного объекта в другой в цепочке прототипов — к ним обращаются, поднимаясь по цепочке, как описано выше.
Примечание: Официально нет способа получить доступ к объекту прототипа объекта напрямую — «ссылки» между элементами в цепочке определены во внутреннем свойстве, называемом [[prototype]] в спецификации для языка JavaScript ( см. ECMAScript). Однако у большинства современных браузеров есть свойство, доступное для них под названием __proto__ (это 2 подчёркивания с обеих сторон), который содержит объект-прототип объекта-конструктора. Например, попробуйте person1.__proto__ и person1.__proto__.__proto__ , чтобы увидеть, как выглядит цепочка в коде!
С ECMAScript 2015 вы можете косвенно обращаться к объекту прототипа объекта Object.getPrototypeOf (obj) .
Свойство prototype: Где определены унаследованные экземпляры
Итак, где определены наследуемые свойства и методы? Если вы посмотрите на страницу со ссылкой Object , вы увидите в левой части большое количество свойств и методов — это намного больше, чем количество унаследованных членов, доступных для объекта person1 . Некоторые из них унаследованы, а некоторые нет — почему это?
Как упоминалось выше, наследованные свойства это те, что определены в свойстве prototype (вы можете называть это подпространством имён), то есть те, которые начинаются с Object.prototype. , а не те, которые начинаются с простого Object . Значение свойства prototype — это объект, который в основном представляет собой контейнер для хранения свойств и методов, которые мы хотим наследовать объектами, расположенными дальше по цепочке прототипов.
Таким образом Object.prototype.watch() , Object.prototype.valueOf() и т. д. доступны для любых типов объектов, которые наследуются от Object.prototype , включая новые экземпляры объектов, созданные из конструктора Person() .
Object.is() , Object.keys() и другие члены, не определённые в контейнере prototype , не наследуются экземплярами объектов или типами объектов, которые наследуются от Object.prototype . Это методы / свойства, доступные только в конструкторе Object() .
Примечание: Это кажется странным — как у вас есть метод, определённый для конструктора, который сам по себе является функцией? Ну, функция также является типом объекта — см. Ссылку на конструктор Function() , если вы нам не верите.
- Вы можете проверить существующие свойства прототипа для себя — вернитесь к нашему предыдущему примеру и попробуйте ввести следующее в консоль JavaScript:
Person.prototype;
Object.prototype;
Вы увидите большое количество методов, определённых для свойства prototype Object ‘а , которые затем доступны для объектов, которые наследуются от Object , как показано выше.
Вы увидите другие примеры наследования цепочек прототипов по всему JavaScript — попробуйте найти методы и свойства, определённые на прототипе глобальных объектов String , Date , Number и Array , например. Все они имеют несколько элементов, определённых на их прототипе, поэтому, например, когда вы создаёте строку, вот так:
var myString = "This is my string.";
В myString сразу есть множество полезных методов, таких как split() , indexOf() , replace() и т. д.
Предупреждение: Важно: Свойство prototype является одной из наиболее противоречивых названий частей JavaScript — вы можете подумать, что this указывает на объект прототипа текущего объекта, но это не так (это внутренний объект, к которому можно получить доступ __proto__ , помните ?). prototype вместо этого — свойство, содержащее объект, на котором вы определяете членов, которые вы хотите наследовать.
Снова create()
Ранее мы показали, как метод Object.create() может использоваться для создания нового экземпляра объекта.
-
Например, попробуйте это в консоли JavaScript предыдущего примера:
var person2 = Object.create(person1);
.__proto__;
Это вернёт объект person1.
Свойство constructor
Каждая функция-конструктор имеет свойство prototype , значением которого является объект, содержащий свойство constructor . Это свойство constructor указывает на исходную функцию-конструктор. Как вы увидите в следующем разделе, свойства, определённые в свойстве Person.prototype (или в общем случае в качестве свойства прототипа функции конструктора, который является объектом, как указано в предыдущем разделе) становятся доступными для всех объектов экземпляра, созданных с помощью конструктор Person() . Следовательно, свойство конструктора также доступно для объектов person1 и person2 .
-
Например, попробуйте эти команды в консоли:
.constructor; person2.constructor;
var person3 = new person1.constructor("Karen", "Stephenson", 26, "female", [ "playing drums", "mountain climbing", ]);
.name.first; person3.age; person3.bio();
Это хорошо работает. Вам не нужно будет использовать его часто, но это может быть действительно полезно, если вы хотите создать новый экземпляр и не имеете ссылки на исходный конструктор, который легко доступен по какой-либо причине.
Свойство constructor имеет другие применения. Например, если у вас есть экземпляр объекта и вы хотите вернуть имя конструктора этого экземпляра, вы можете использовать следующее:
.constructor.name;
Например, попробуйте это:
.constructor.name;
Примечание: Значение constructor.name может измениться (из-за прототипического наследования, привязки, препроцессоров, транспилеров и т. д.), Поэтому для более сложных примеров вы захотите использовать оператор instanceof .
Изменение прототипов
Давайте рассмотрим пример изменения свойства prototype функции-конструктора — методы, добавленные в прототип, затем доступны для всех экземпляров объектов, созданных из конструктора.
-
Вернитесь к нашему примеру oojs-class-further-exercises.html и создайте локальную копию исходного кода. Ниже существующего JavaScript добавьте следующий код, который добавляет новый метод в свойство prototype конструктора:
Person.prototype.farewell = function () alert(this.name.first + " has left the building. Bye for now!"); >;
.farewell();
Должно появиться всплывающее окно, с именем пользователя, определённым в конструкторе. Это действительно полезно, но ещё более полезно то, что вся цепочка наследования обновляется динамически, автоматически делая этот новый метод доступным для всех экземпляров объектов, полученных из конструктора.
Подумайте об этом на мгновение. В нашем коде мы определяем конструктор, затем мы создаём экземпляр объекта из конструктора, затем добавляем новый метод к прототипу конструктора:
function Person(first, last, age, gender, interests) // определения свойств и методов > var person1 = new Person("Tammi", "Smith", 32, "neutral", [ "music", "skiing", "kickboxing", ]); Person.prototype.farewell = function () alert(this.name.first + " has left the building. Bye for now!"); >;
Но метод farewell() по-прежнему доступен в экземпляре объекта person1 — его элементы были автоматически обновлены, чтобы включить недавно определённый метод farewell() .
Примечание: Если у вас возникли проблемы с получением этого примера для работы, посмотрите на наш пример oojs-class-prototype.html (см. также это running live).
Вы редко увидите свойства, определённые в свойстве prototype , потому что они не очень гибки при таком определении. Например, вы можете добавить свойство следующим образом:
Person.prototype.fullName = "Bob Smith";
Это не очень гибко, так как человека нельзя назвать так. Было бы намного лучше сделать это, создав fullName из name.first и name.last :
Person.prototype.fullName = this.name.first + " " + this.name.last;
Однако это не работает, поскольку в этом случае this будет ссылаться на глобальную область, а не на область функции. Вызов этого свойства вернёт undefined undefined . Это отлично работало с методом, который мы определили ранее в прототипе, потому что он находится внутри области функций, которая будет успешно перенесена в область экземпляра объекта. Таким образом, вы можете определить постоянные свойства прототипа (т. е. те, которые никогда не нуждаются в изменении), но обычно лучше определять свойства внутри конструктора.
Фактически, довольно распространённый шаблон для большего количества определений объектов — это определение свойств внутри конструктора и методов в прототипе. Это упрощает чтение кода, поскольку конструктор содержит только определения свойств, а методы разделены на отдельные блоки. Например:
// Определение конструктора и его свойств function Test(a, b, c, d) // определение свойств. > // Определение первого метода Test.prototype.x = function() . >; // Определение второго метода Test.prototype.y = function() . >; //. и так далее
Этот образец можно увидеть в действии в примере приложения плана школы Петра Залевы.
Резюме
В этой статье рассмотрены прототипы объектов JavaScript (в том числе и то, как прототип цепочки объектов позволяет объектам наследовать функции друг от друга), свойство прототипа и как его можно использовать для добавления методов к конструкторам и другие связанные с этой статьёй темы.
В следующей статье мы рассмотрим то, как вы можете реализовать наследование функциональности между двумя собственными настраиваемыми объектами.
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on 2 дек. 2023 г. by MDN contributors.
Your blueprint for a better internet.