что такое прототип объекта
Прототип объекта
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/prototype-inheritance.
Объекты в JavaScript можно организовать в цепочки так, чтобы свойство, не найденное в одном объекте, автоматически искалось бы в другом.
Прототип proto
Свойство __proto__ доступно во всех браузерах, кроме IE10-, а в более старых IE оно, конечно же, тоже есть, но напрямую к нему не обратиться, требуются чуть более сложные способы, которые мы рассмотрим позднее.
Пример кода (кроме IE10-):
Иллюстрация происходящего при чтении rabbit.eats (поиск идёт снизу вверх):
Обратим внимание – прототип используется исключительно при чтении. Запись значения, например, rabbit.eats = value или удаление delete rabbit.eats – работает напрямую с объектом.
Другими словами, прототип – это «резервное хранилище свойств и методов» объекта, автоматически используемое при поиске.
Метод hasOwnProperty
Обычный цикл for..in не делает различия между свойствами объекта и его прототипа.
Он перебирает всё, например:
Иногда хочется посмотреть, что находится именно в самом объекте, а не в прототипе.
Для того, чтобы перебрать свойства самого объекта, достаточно профильтровать key через hasOwnProperty :
Object.create(null)
Зачастую объекты используют для хранения произвольных значений по ключу, как коллекцию:
Это может быть неприятным сюрпризом и приводить к ошибкам, если названия свойств приходят от посетителя и могут быть произвольными.
Чтобы этого избежать, мы можем исключать свойства, не принадлежащие самому объекту:
Однако, есть путь и проще:
Объект, создаваемый при помощи Object.create(null) не имеет прототипа, а значит в нём нет лишних свойств. Для коллекции – как раз то, что надо.
Методы для работы с proto
Чтение: Object.getPrototypeOf(obj) Возвращает obj.__proto__ (кроме IE8-) Запись: Object.setPrototypeOf(obj, proto) Устанавливает obj.__proto__ = proto (кроме IE10-).
Кроме того, есть ещё один вспомогательный метод:
Итого
В современных браузерах есть методы для работы с прототипом:
Также мы рассмотрим, как свойство __proto__ используется внутри самого языка JavaScript и как организовать классы с его помощью.
Встроенные прототипы
Свойство «prototype» широко используется внутри самого языка JavaScript. Все встроенные функции-конструкторы используют его.
Сначала мы рассмотрим детали, а затем используем «prototype» для добавления встроенным объектам новой функциональности.
Object.prototype
Давайте выведем пустой объект:
Вот что происходит:
Когда вызывается new Object() (или создаётся объект с помощью литерала <. >), свойство [[Prototype]] этого объекта устанавливается на Object.prototype по правилам, которые мы обсуждали в предыдущей главе:
Мы можем проверить это так:
Обратите внимание, что по цепочке прототипов выше Object.prototype больше нет свойства [[Prototype]] :
Другие встроенные прототипы
Вот более полная картина (для трёх встроенных объектов):
Давайте проверим прототипы:
В браузерных инструментах, таких как консоль разработчика, можно посмотреть цепочку наследования (возможно, потребуется использовать console.dir для встроенных объектов):
Примитивы
Самое сложное происходит со строками, числами и булевыми значениями.
Специальные значения null и undefined стоят особняком. У них нет объектов-обёрток, так что методы и свойства им недоступны. Также у них нет соответствующих прототипов.
Изменение встроенных прототипов
В течение процесса разработки у нас могут возникнуть идеи о новых встроенных методах, которые нам хотелось бы иметь, и искушение добавить их во встроенные прототипы. Это плохая идея.
Так что, в общем, изменение встроенных прототипов считается плохой идеей.
В современном программировании есть только один случай, в котором одобряется изменение встроенных прототипов. Это создание полифилов.
Полифил – это термин, который означает эмуляцию метода, который существует в спецификации JavaScript, но ещё не поддерживается текущим движком JavaScript.
Тогда мы можем реализовать его сами и добавить во встроенный прототип.
Заимствование у прототипов
В главе Декораторы и переадресация вызова, call/apply мы говорили о заимствовании методов.
Это когда мы берём метод из одного объекта и копируем его в другой.
Некоторые методы встроенных прототипов часто одалживают.
Например, если мы создаём объект, похожий на массив (псевдомассив), мы можем скопировать некоторые методы из Array в этот объект.
Но это будет невозможно, если obj уже наследует от другого объекта. Помните, мы можем наследовать только от одного объекта одновременно.
Заимствование методов – гибкий способ, позволяющий смешивать функциональность разных объектов по необходимости.
Итого
Задачи
Добавить функциям метод «f.defer(ms)»
Прототипы это объекты (и почему это важно)
JavaScript – один из главных языков нашего стека в Хекслете. Мы используем ReactJS и NodeJS в интерактивных частях платформы, и сделали вводный курс (более продвинутые – на подходе). Любовь к JS помогла опубликовать этот перевод хорошего эссе «Prototypes are Objects (and why that matters)».
Этот пост рассчитан на тех, кто знаком с объектами в JavaScript и знает, как прототип определяет поведение объекта, что такое функция-конструктор и как свойство .property конструктора относится к объекту, который он конструирует. Общее понимание синтаксиса ECMAScript 2015 тоже не помешает.
Мы всегда могли создать класс в JavaScript таким образом:
Person это функция-конструктор, а также класс в JavaScript’овом понимании этого слова. ECMAScript 2015 дает возможность использовать ключевое слово class и т.н. “compact method notation”. Это синтаксический сахар для написания функций и присвоения методов его прототипу (там все чуть сложнее, но сейчас это не важно). Так что мы можем написать класс Person вот так:
Клево. Но под капотом все равно есть функция-конструктор с привязкой к имени Person, и есть объект Person.prototype, который выглядит так:
Прототипы это объекы
Если нужно изменить поведение объекта в JavaScript, можно добавить, удалить или изменить методы объекта через добавление, удаление или изменение функций, привязанных к свойствам этого объекта. В этом отличие от многих “классических” языков, в которых есть специальная форма (например, в Руби есть def) для задания методов.
Прототипы в JavaScript это “всего лишь объекты”, и благодаря этому мы можем добавлять, удалять или изменять методы прототипа через добавление, удаление или изменение функций, привязанных к свойствам этого прототипа.
Именно это и делает ECMAScript 5 код выше, и синтаксис class “рассахаривает” его в эквивалентный код.
Прототипы это “всего лишь объекты”, и это означает, что мы можем использовать любые техники, которые работают на объектах. Например, вместо привязки одной функции к прототипу, мы можем совершать массовую привязку с помощью Object.assign:
И, конечно, мы можем использовать компактный синтаксис если захотим:
Mixins (примеси)
Так как class “рассахаривает” код в конструктор-функции и прототипы, мы можем использовать примеси вот так:
Мы только что “вмешали” методы по сбору книг в класс Person. Круто, что можно вот так просто писать код, но можно и давать названия:
Так можно продолжать сколько захочется:
Зачем использовать примеси
Сборка классов с помощью базовой функциональности (Person) и миксинов (BookCollector и Author) дает некоторые преимущества. Во-первых, иногда функциональность невозможно хорошо разложить на части в красивой древовидной структуре. Авторы книг могут быть корпорациями, а не людьми. И антикварные книжные лавки собирают книги так же, как книголюбы.
Такие примеси, как BookCollector или Author могут быть вмешаны в несколько разных классов. Попытки композиции функциональности с помощью наследования не всегда удачны.
Еще одно преимущество не так очевидно в простом примере, но в продакшн-системах классы могут разрастаться до нелепых размеров. Даже если примесь не используется в нескольких классах, декомпозиция большого класса с помощью примесей помогает удовлетворить принцип принцип единственной обязанности. Каждый миксин может иметь тольк одну область ответственности. Все это упрощает понимание и тестирование.
почему это важно
Существуют другие способы декомпозиции ответственности в классах (например, делегирование и композиция), но суть в том, что если вы решили использовать mixins, то это очень простой путь, потому что в JavaScript нет большого и сложного механизма ООП, который бы загонял вас в четкие рамки.
Например, в Руби использоват миксины легко, потому что с самого начала там есть специальная фича – модули. В других ОО-языках использовать миксины сложно, потому что система классов не поддерживает их, и они не очень вяжутся с мета-программированием.
JavaScript парадигма объектов и прототипов. Простое объяснение.
Многим новым разработчикам, особенно тем, кто привык работать с традиционным ООП, работать в мире JavaScript может показаться не удобно и не привычно. Для них код на JavaScript может выглядеть грязным и запутанным. Данная статья это попытка объяснить, максимально простым языком, что такое объекты в JavaScript и рассказать о механизме наследования на основе прототипов.
Хочу отметить, что я читал, множество статей, пытающихся объяснить что такое объекты в JavaScript, но я, никогда не читал те, которые объясняли бы их полностью или четко. Они часто сосредотачиваются исключительно на наследовании, оставляя без внимания другие важные моменты или не приводят достаточного количества примеров.
Что такое объекты?
Объекты на самом базовом уровне можно представить как список пар ключ / значение, причем ключ всегда является строкой, …а значение…. может быть чем-то иным. Это похоже на то, что вы можете назвать «картой» или «словарем» на других языках. Все, базовые сущности которые вы обычно создаете в JavaScript, и которое не является примитивами, является объектами. Объекты облегчают упаковку и перемещение данных, а создание новых объектов в JavaScript более тривиально, чем в других объектно-ориентированных, таких как Java / C #.
Когда говорят об объектах часто упоминают термин “свойство”. Этот термин означает определенную пару ключ/значение. Чтобы дать вам представление о том, как выглядят объекты, мы начнем с простого примера объекта с двумя свойствами: age и weight.
Это фрагмент кода демонстрирует использования литеральной (инициирующей) нотации объекта. Объектный литерал не является переменной или возвращаемым значением.
Функции являются объектами
Как было замечено раньше, в Javascript все что не примитивы являются объектами, включая функции… Я знаю это может показаться странным. Сложно думать о функции как о группе пар ключ/значение. Так как функции объекты, их часто еще называют как объекты-фукнции. Это специальные группа пар ключ/значение с особыми свойствами для выполнения кода и передачи значений. Мы рассмотрим эти свойства в следующем разделе. В начале давайте поговорим почему функции так важны.
Можно сказать, что объекты-функции имеют две основные цели. Если мы хотим создать блок логики, который выполняется, мы можем использовать объект-функцию: точно так же, как «методы» в любом другом языке программирования. Другой целью является то, что если мы захотим создать объекты со значениями и методами, и возможно, с некоторой логикой для установки этих значений, мы также будем использовать объекты-функции. Здесь вы можете думать о объектах-функциях как о «классах», которые ведут себя как объектно-ориентированные языки (то есть Java / C #).
В стандартном случае функции в JavaScript выглядят как функции на любом другом языке; они выполняют логику для выполнения конкретной задачи.
Если мы хотим упаковать небольшую группу данных, как, например, два наших свойства в объекте Dog, то достаточно простой список пар ключ / значение. Но что если мы захотим создать несколько объектов Dog? Может быть, некоторые значения должны быть статическими, а другие динамическими. В данном случае нам понадобятся объекты-функции. Когда мы вызываем функцию с помощью new, объект (он же экземпляр-объект) вернется со свойствами установленными с помощью ключевого слова this внутри функции.
Объекты против Прототипов
Теперь давайте поговорим о прототипах. Вы часто слышали, что JavaScript — это язык на основе прототипов. Значит ли это, что объекты и прототипы — это одно и то же? Ну, не совсем. Прототипы являются особым типом объекта и существуют как свойство для объектов-функций. Когда мы пытаемся получить доступ к ключу объекта-функции, JavaScript проверяет его свойство prototype, чтобы увидеть, есть ли оно там. Если нет, он пойдет вверх по цепочке прототипов, чтобы попытаться найти его. Чтобы понять цепочку прототипов, нам нужно узнать о функциях и наследовании.
Функции и наследование
Всякий раз, когда экземпляр объекта возвращается из вызова функции с использованием new, ему присваивается свойство с ключом __proto__. Значение этого свойства является свойство prototype функции, которая его создала.
Если мы попытаемся получить доступ к свойству объекта-экземпляра, а его там нет, JavaScript сначала перейдет к __proto__ и проверит, находится ли оно в прототипе родительской функции. Чтобы увидеть это в действии, давайте установим свойство в одном из атрибутов prototype нашего объекта Dog, и когда мы вызовем Spot[‘whatever the key name is’] или Bingo[‘whatever the key name is’], мы получим то же значение. Это будет работать даже после того, как будут созданы оба объекта-экземпляра dog.
Создание методов таким способом (в отличие от использования this внутри функций) особенно полезна, потому что создание метода будет происходить только один раз, а не каждый раз, когда вызывается new. Это будет экономить память и увеличит производительность.
Рассмотрим наследование немного глубже
Давайте рассмотрим несколько примеров JavaScript, движущихся вверх по цепочке прототипов для доступа к hasOwnProperty у некоторых объектов.
Литеральный объект (Object-literal):
Объект Экземпляр (Instance-Object):
Объект Функция (Function-object):
А как насчет __proto__ у объектов-функций?
Как уже говорилось, __proto__ помогает связать объекты с прототипами, от которых они наследуются. А как насчет вызова __proto__ непосредственно для объектов-функций? JavaScript действительно имеет встроенный объект-функцию под названием Function. Свойство __proto__ каждой функции указывает на Function.prototype, который является функцией, но не имеет свойства prototype и возвращает неопределенное значение. Function.prototype определяет поведение по умолчанию, от которого наследуются все функции. Как и все свойства прототипов функциональных объектов, он по-прежнему имеет __proto__, который указывает на Object.prototype.
Выше сказанное можно продемонстрировать следующей картинкой. Обратите внимание, что Object.prototype — это то, откуда все происходит.
Многоуровневое наследование
Когда мы говорим о наследовании, мы обычно думаем об объектах экземпляра, возвращаемых из функций. С прототипом вы также можете создать несколько уровней наследования и иметь объекты-функции, наследуемые от других объектов-функций. Все, что вам нужно сделать, это установить прототип дочернего объекта-функции в другой экземпляр прототипа родительского объекта-функции. Тогда все свойства родителя будут скопированы. Если родительская функция получает аргументы, такие как age и weight у Dog, используйте .call, чтобы установить свойство this дочернего объекта.
Labrador наследуется от Dog:
Классы
Классы в JavaScript, созданные в ES6, являются просто синтаксическим сахаром над объектами-функциями. Вместо того, чтобы набирать прототип снова и снова, чтобы определить методы для функций, с ключевым словом class мы можем просто определить группу методов внутри класса. С помощью ключевого слова extends классы могут наследоваться от других классов без необходимости использовать Object.create и Object.call. Лично мне больше нравится использовать классы, но имейте в виду, что старые браузеры могут их не поддерживать. Для решения этой проблемы есть такие инструменты, как Babel.
Использование объектов функций:
Аналогичный код но с использованием классов:
Объекты против примитивов
Код JavaScript по сути сводится к двум основным типам: примитивам и объектам. В JavaScript есть 5 примитивов: boolean, number, string, null и undefined. Примитивы — это всего лишь простые значения без свойств. Три примитива: boolean, number и string имеют дубликаты объектов, которые JavaScript использует как оболочку во время определенных операций. Например, «some string».length вызовет new String() и вернет объект-экземпляр, обернутый вокруг строкового примитива, чтобы можно было получить доступ к свойству length. Как уже упоминалось, все объекты-экземпляры наследуются от Object. Так что со строкой вы можете использовать методы родительского объекта, например тот же hasOwnProperty.
Прототипы объектов
Базовая компьютерная грамотность, базовое понимание HTML и CSS, знакомство с основами JavaScript (см. Первые шаги и Строительные блоки) и основы OOJS (см. Введение в объекты).
Понять прототипы объектов JavaScript, как работают прототипные цепочки и как добавить новые методы в prototype свойство.
Язык основанный на прототипах?
JavaScript часто описывают как язык прототипного наследования — каждый объект, имеет объект-прототип, который выступает как шаблон, от которого объект наследует методы и свойства. Объект-прототип так же может иметь свой прототип и наследовать его свойства и методы и так далее. Это часто называется цепочкой прототипов и объясняет почему одним объектам доступны свойства и методы которые определены в других объектах.
Точнее, свойства и методы определяются в свойстве prototype функции-конструктора объектов, а не в самих объектах.
Давайте посмотрим на пример, чтобы стало понятнее.
Понимание прототипа объектов
В этом примере мы определили конструктору функцию, например:
Затем мы создаём экземпляр объекта следующим образом:
Если вы наберёте « person1. » в вашей консоли JavaScript, вы должны увидеть, что браузер пытается автоматически заполнить это с именами участников, доступных на этом объекте:
Свойство prototype: Где определены унаследованные экземпляры
Снова create()
Ранее мы показали, как метод Object.create() может использоваться для создания нового экземпляра объекта.
Свойство constructor
Это хорошо работает. Вам не нужно будет использовать его часто, но это может быть действительно полезно, если вы хотите создать новый экземпляр и не имеете ссылки на исходный конструктор, который легко доступен по какой-либо причине.
Свойство constructor имеет другие применения. Например, если у вас есть экземпляр объекта и вы хотите вернуть имя конструктора этого экземпляра, вы можете использовать следующее:
Например, попробуйте это:
Изменение прототипов
Давайте рассмотрим пример изменения свойства prototype функции-конструктора — методы, добавленные в прототип, затем доступны для всех экземпляров объектов, созданных из конструктора.
Должно появиться всплывающее окно, с именем пользователя, определённым в конструкторе. Это действительно полезно, но ещё более полезно то, что вся цепочка наследования обновляется динамически, автоматически делая этот новый метод доступным для всех экземпляров объектов, полученных из конструктора.
Подумайте об этом на мгновение. В нашем коде мы определяем конструктор, затем мы создаём экземпляр объекта из конструктора, затем добавляем новый метод к прототипу конструктора:
Примечание: Если у вас возникли проблемы с получением этого примера для работы, посмотрите на наш пример oojs-class-prototype.html (см. также это running live).
Это не очень гибко, так как человека нельзя назвать так. Было бы намного лучше сделать это, создав fullName из name.first и name.last :
Этот образец можно увидеть в действии в примере приложения плана школы Петра Залевы.
Резюме
В этой статье рассмотрены прототипы объектов JavaScript (в том числе и то, как прототип цепочки объектов позволяет объектам наследовать функции друг от друга), свойство прототипа и как его можно использовать для добавления методов к конструкторам и другие связанные с этой статьёй темы.
В следующей статье мы рассмотрим то, как вы можете реализовать наследование функциональности между двумя собственными настраиваемыми объектами.