Объявлена функция function f чем является f prototype
Свойство "prototype" широко используется внутри самого языка JavaScript. Все встроенные функции-конструкторы используют его.
Сначала мы рассмотрим детали, а затем используем "prototype" для добавления встроенным объектам новой функциональности.
Задачи
Добавить функциям метод "f.defer(ms)"
Добавьте всем функциям в прототип метод defer(ms) , который вызывает функции через ms миллисекунд.
Примитивы
Самое сложное происходит со строками, числами и булевыми значениями.
Как мы помним, они не объекты. Но если мы попытаемся получить доступ к их свойствам, то тогда будет создан временный объект-обёртка с использованием встроенных конструкторов String , Number и Boolean , который предоставит методы и после этого исчезнет.
Эти объекты создаются невидимо для нас, и большая часть движков оптимизирует этот процесс, но спецификация описывает это именно таким образом. Методы этих объектов также находятся в прототипах, доступных как String.prototype , Number.prototype и Boolean.prototype .
Значения null и undefined не имеют объектов-обёртокСпециальные значения null и undefined стоят особняком. У них нет объектов-обёрток, так что методы и свойства им недоступны. Также у них нет соответствующих прототипов.
Свойство F.prototype и создание объектов через new
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
До этого момента мы говорили о наследовании объектов, объявленных через <. >.
Но в реальных проектах объекты обычно создаются функцией-конструктором через new . Посмотрим, как указать прототип в этом случае.
Заимствование у прототипов
В главе Декораторы и переадресация вызова, call/apply мы говорили о заимствовании методов.
Это когда мы берём метод из одного объекта и копируем его в другой.
Некоторые методы встроенных прототипов часто одалживают.
Например, если мы создаём объект, похожий на массив (псевдомассив), мы можем скопировать некоторые методы из Array в этот объект.
Это работает, потому что для внутреннего алгоритма встроенного метода join важны только корректность индексов и свойство length , он не проверяет, является ли объект на самом деле массивом. И многие встроенные методы работают так же.
Альтернативная возможность – мы можем унаследовать от массива, установив obj.__proto__ как Array.prototype , таким образом все методы Array станут автоматически доступны в obj .
Но это будет невозможно, если obj уже наследует от другого объекта. Помните, мы можем наследовать только от одного объекта одновременно.
Заимствование методов – гибкий способ, позволяющий смешивать функциональность разных объектов по необходимости.
Задачи
Прототип после создания
В примерах ниже создаётся объект new Rabbit , а затем проводятся различные действия с prototype .
Каковы будут результаты выполнения? Почему?
Начнём с этого кода. Что он выведет?
Добавили строку (выделена), что будет теперь?
А если код будет такой? (заменена одна строка):
А такой? (заменена одна строка)
И последний вариант:
Результат: true , из прототипа
Результат: true . Свойство prototype всего лишь задаёт __proto__ у новых объектов. Так что его изменение не повлияет на rabbit.__proto__ . Свойство eats будет получено из прототипа.
Результат: false . Свойство Rabbit.prototype и rabbit.__proto__ указывают на один и тот же объект. В данном случае изменения вносятся в сам объект.
Результат: true , так как delete rabbit.eats попытается удалить eats из rabbit , где его и так нет. А чтение в alert произойдёт из прототипа.
Результат: undefined . Удаление осуществляется из самого прототипа, поэтому свойство rabbit.eats больше взять неоткуда.
Аргументы по умолчанию
Есть функция Menu , которая получает аргументы в виде объекта options :
Ряд опций должны иметь значение по умолчанию. Мы могли бы проставить их напрямую в объекте options :
…Но такие изменения могут привести к непредвиденным результатам, т.к. объект options может быть повторно использован во внешнем коде. Он передаётся в Menu для того, чтобы параметры из него читали, а не писали.
Один из способов безопасно назначить значения по умолчанию – скопировать все свойства options в локальные переменные и затем уже менять. Другой способ – клонировать options путём копирования всех свойств из него в новый объект, который уже изменяется.
При помощи наследования и Object.create предложите третий способ, который позволяет избежать копирования объекта и не требует новых переменных.
Можно прототипно унаследовать от options и добавлять/менять опции в наследнике:
Все изменения будут происходить не в исходном options , а в его наследнике, при этом options останется незатронутым.
Прототипы в JS и малоизвестные факты
Получив в очередной раз кучу вопросов про прототипы на очередном собеседовании, я понял, что слегка подзабыл тонкости работы прототипов, и решил освежить знания. Я наткнулся на кучу статей, которые были написаны либо по наитию автора, как он "чувствует" прототипы, либо статья была про отдельную часть темы и не давала полной картины происходящего.
Оказалось, что есть много неочевидных вещей из старых времён ES5 и даже ES6, о которых я не слышал. А еще оказалось, что вывод консоли браузера может не соответствовать действительности.
Что такое прототип
Объект в JS имеет собственные и унаследованные свойства, например, в этом коде:
у объекта foo имеется собственное свойство bar со значением 1 , но также имеются и другие свойства, такие как toString . Чтобы понять, как объект foo получает новое свойство toString , посмотрим на то, из чего состоит объект:
Дело в том, что у объекта есть ссылка на другой объект-прототип. При доступе к полю foo.toString сначала выполняется поиск такого свойства у самого объекта, а потом у его прототипа, прототипа его прототипа, и так пока цепочка прототипов не закончится. Это похоже на односвязный список объектов, где поочередно проверяется объект и его объекты-прототипы. Так реализовано наследование свойств, например, у (почти, но об этом позже) любого объекта есть методы valueOf и toString .
Как выглядит прототип
У всех прототипов имеются два общих свойства, constructor и __proto__ . Свойство constructor указывает на функцию-конструктор, с помощью которой создавался объект, а свойство __proto__ указывает на следующий прототип в цепочке (либо null, если это последний прототип). Остальные свойства доступны через . , как в примере выше.
Да кто такой этот ваш constructor
constructor – это ссылка на функцию, с помощью которой был создан объект:
Не совсем понятна идея зачем он был нужен, возможно, как способ клонирования объекта:
Но я не нашел подходящий пример его использования, если у Вас есть примеры проектов, где это использовалось, то напишите об этом. В остальном же использовать constructor лучше не стоит, так как это writable свойство, которое можно случайно перезаписать, работая с прототипом, и сломать часть логики.
Где живёт прототип
На самом деле, объекты представляют собой не только поля, доступные для JS кода. Интерпретатор также сохраняет некоторые приватные данные объекта для работы с ним, для этого в стандарте определено понятие внутренних слотов, которые обозначены как имя в квадратных скобках [[SlotName]] . Для прототипов отведен приватный слот [[Prototype]] содержащий ссылку на объект-прототип (либо null , если прототипа нет).
Из-за того, что [[Prototype]] предназначался исключительно для самого JS движка, получить доступ к прототипу объекта было невозможно. Для случаев когда это было нужно, ввели нестандартное свойство __proto__ , которое поддержали многие браузеры и которое по итогу попало в сам стандарт, но как опциональное и стандартизированное только для обратной совместимости с существующим JS кодом.
О чем вам недоговаривает дебаггер, или он вам не прототип
Свойство __proto__ является геттером и сеттером для внутреннего слота [[Prototype]] и находится в Object.prototype :
Из-за этого я избегал записи __proto__ для обозначения прототипа. __proto__ находится не в самом объекте, что приводит к неожиданным результатам. Для демонстрации попробуем через __proto__ удалить прототип объекта и затем восстановить его:
Как так получилось? Дело в том, что __proto__ – это унаследованное свойство Object.prototype , а не самого объекта foo . Из-за этого в момент когда в цепочке прототипов пропадает ссылка на Object.prototype , __proto__ превращается в тыкву и перестает работать с прототипом.
А теперь отработаем кликбейт из введения. Представим следующую цепочку прототипов:
В консоли Chrome foo будет выглядеть следующим образом:
А теперь уберем связь между baz и Object.prototype :
И теперь в консоли Chrome видим следующий результат:
Связь с Object.prototype разорвана у baz и __proto__ возвращает undefined даже у дочернего объекта foo , однако Chrome все равно показывает что __proto__ есть. Скорее всего тут имеется в виду внутренний слот [[Prototype]] , но для простоты это было изменено на __proto__ , ведь если не извращаться с цепочкой прототипов, это будет верно.
Как работать с прототипом объекта
Рассмотрим основные способы работы с прототипом: изменение прототипа и создание нового объекта с указанным прототипом.
Для изменения прототипа у существующего объекта есть всего два метода: использование сеттера __proto__ и метод Object.setPrototypeOf .
Если браузер не поддерживает ни один из этих методов, то изменить прототип объекта невозможно, можно только создать его копию с новым прототипом.
Но есть один нюанс с внутренним слотом [[Extensible]] который указывает на то, возможно ли добавлять к нему новые поля и менять его прототип. Есть несколько функций, которые выставляют этот флаг в false и предотвращают смену прототипа: Object.freeze , Object.seal , Object.preventExtensions . Пример:
А теперь менее категоричный вопрос создания нового объекта с прототипом. Для этого есть следующие способы.
Стандартный способ:
Если нет поддержки Object.create , но есть __proto__ :
И в случае если отсутствует поддержка всего вышеперечисленного:
Способ основан на логике работы оператора new , о которой поговорим чуть ниже. Но сам способ основан на том, что оператор new берет свойство prototype функции и использует его в качестве прототипа, т.е. устанавливает объект в [[Prototype]] , что нам и нужно.
Функции и конструкторы
А теперь поговорим про функции и как они работают в качестве конструкторов.
Функция Person тут является конструктором и создает два поля в новом объекте, а цепочка прототипов выглядит так:
Откуда взялся Person.prototype ? При объявлении функции, у нее автоматически создается свойство prototype для того чтобы ее можно было использовать как конструктор (note 3), таким образом свойство prototype функции не имеет отношения к прототипу самой функции, а задает прототипы для дочерних объектов. Это позволит реализовывать наследование и добавлять новые методы, например так:
И теперь вызов user.fullName() вернет строку "John Doe".
Что такое new
На самом деле оператор new не таит в себе никакой магии. При вызове new выполняет несколько действий:
- Создает новый объект self
- Записывает свойство prototype функции конструктора в прототип объекта self
- Вызывает функцию конструктор с объектом self в качестве аргумента this
- Возвращает self если конструктор вернул примитивное значение, иначе возвращает значение из конструктора
Все эти действия можно сделать силами самого языка, поэтому можно написать свой собственный оператор new в виде функции:
Но начиная с ES6 волшебство пришло и к new в виде свойства new.target, которое позволяет определить, была ли вызвана функция как конструктор с new, или как обычная функция:
new.target будет undefined для обычного вызова функции, и ссылкой на саму функцию в случае вызова через new ;
Наследование
Зная все вышеперечисленное, можно сделать классическое наследование дочернего класса Student от класса Person . Для этого нужно
- Создать конструктор Student с вызовом логики конструктора Person
- Задать объекту `Student.prototype` прототип от `Person`
- Добавить новые методы к `Student.prototype`
Фиолетовым цветом обозначены поля объекта (они все находятся в самом объекте, т.к. this у всей цепочки прототипов один), а методы желтым (находятся в прототипах соответствующих функций)
Вариант 1 предпочтительнее, т.к. Object.setPrototypeOf может привести к проблемам с производительностью.
Сколько вам сахара к классу
Для того чтобы облегчить классическую схему наследование и предоставить более привычный синтаксис, были представлены классы, просто сравним код с примерами Person и Student:
Уменьшился не только бойлерплейт, но и поддерживаемость:
- В отличие от функции конструктора, при вызове конструктора без new выпадет ошибка
- Родительский класс указывается ровно один раз при объявлении
При этом цепочка прототипов получается идентичной примеру с явным указанием prototype у функций конструкторов.
Наивно было бы ожидать, что одна статья ответит на все вопросы. Если у Вас есть интересные вопросы, экскурсы в историю, аргументированные или беспочвенные заявления о том, что я сделал все не так, либо правки по ошибкам, пишите в комментарии.
P. P. S.
К сожалению главный кликбейт статьи перестал быть актуальным. В данный момент Chrome (версия 93, на момент обновления статьи) перестал использовать __proto__ для обозначения прототипа, и теперь отображает его как слот [[Prototype]] :
Справедливости ради хочу отметить что в Firefox (92) также не используется обозначение __proto__ :
Итого
- Все встроенные объекты следуют одному шаблону:
- Методы хранятся в прототипах ( Array.prototype , Object.prototype , Date.prototype и т.д.).
- Сами объекты хранят только данные (элементы массивов, свойства объектов, даты).
Свойство constructor
У каждой функции по умолчанию уже есть свойство prototype .
Оно содержит объект такого вида:
В коде выше я создал Rabbit.prototype вручную, но ровно такой же – генерируется автоматически.
Можно его использовать для создания объекта с тем же конструктором, что и данный:
Эта возможность бывает полезна, когда, получив объект, мы не знаем в точности, какой у него был конструктор (например, сделан вне нашего кода), а нужно создать такой же.
JavaScript никак не использует свойство constructor . То есть, оно создаётся автоматически, а что с ним происходит дальше – это уже наша забота. В стандарте прописано только его создание.
В частности, при перезаписи Rabbit.prototype = < jumps: true >свойства constructor больше не будет.
Сам интерпретатор JavaScript его в служебных целях не требует, поэтому в работе объектов ничего не «сломается». Но если мы хотим, чтобы возможность получить конструктор, всё же, была, то можно при перезаписи гарантировать наличие constructor вручную:
Либо можно поступить аккуратно и добавить свойства к встроенному prototype без его замены:
Как мы только что видели, с конструкторами всё просто, назначить прототип можно кросс-браузерно при помощи F.prototype .
Теперь небольшое «лирическое отступление» в область совместимости.
Прямые методы работы с прототипом отсутствуют в старых IE, но один из них – Object.create(proto) можно эмулировать, как раз при помощи prototype . И он будет работать везде, даже в самых устаревших браузерах.
Кросс-браузерный аналог – назовём его inherit , состоит буквально из нескольких строк:
Результат вызова inherit(animal) идентичен Object.create(animal) . Она создаёт новый пустой объект с прототипом animal .
Посмотрите внимательно на функцию inherit и вы, наверняка, сами поймёте, как она работает…
Если где-то неясности, то её построчное описание:
- Создана новая функция F . Она ничего не делает с this , так что если вызвать new F , то получим пустой объект.
- Свойство F.prototype устанавливается в будущий прототип proto
- Результатом вызова new F будет пустой объект с __proto__ равным значению F.prototype .
- Мы получили пустой объект с заданным прототипом, как и хотели. Возвратим его.
Для унификации можно запустить такой код, и метод Object.create станет кросс-браузерным:
В частности, аналогичным образом работает библиотека es5-shim, при подключении которой Object.create станет доступен для всех браузеров.
Прототипы в JS и малоизвестные факты
Получив в очередной раз кучу вопросов про прототипы на очередном собеседовании, я понял, что слегка подзабыл тонкости работы прототипов, и решил освежить знания. Я наткнулся на кучу статей, которые были написаны либо по наитию автора, как он "чувствует" прототипы, либо статья была про отдельную часть темы и не давала полной картины происходящего.
Оказалось, что есть много неочевидных вещей из старых времён ES5 и даже ES6, о которых я не слышал. А еще оказалось, что вывод консоли браузера может не соответствовать действительности.
Что такое прототип
Объект в JS имеет собственные и унаследованные свойства, например, в этом коде:
у объекта foo имеется собственное свойство bar со значением 1 , но также имеются и другие свойства, такие как toString . Чтобы понять, как объект foo получает новое свойство toString , посмотрим на то, из чего состоит объект:
Дело в том, что у объекта есть ссылка на другой объект-прототип. При доступе к полю foo.toString сначала выполняется поиск такого свойства у самого объекта, а потом у его прототипа, прототипа его прототипа, и так пока цепочка прототипов не закончится. Это похоже на односвязный список объектов, где поочередно проверяется объект и его объекты-прототипы. Так реализовано наследование свойств, например, у (почти, но об этом позже) любого объекта есть методы valueOf и toString .
Как выглядит прототип
У всех прототипов имеются два общих свойства, constructor и __proto__ . Свойство constructor указывает на функцию-конструктор, с помощью которой создавался объект, а свойство __proto__ указывает на следующий прототип в цепочке (либо null, если это последний прототип). Остальные свойства доступны через . , как в примере выше.
Да кто такой этот ваш constructor
constructor – это ссылка на функцию, с помощью которой был создан объект:
Не совсем понятна идея зачем он был нужен, возможно, как способ клонирования объекта:
Но я не нашел подходящий пример его использования, если у Вас есть примеры проектов, где это использовалось, то напишите об этом. В остальном же использовать constructor лучше не стоит, так как это writable свойство, которое можно случайно перезаписать, работая с прототипом, и сломать часть логики.
Где живёт прототип
На самом деле, объекты представляют собой не только поля, доступные для JS кода. Интерпретатор также сохраняет некоторые приватные данные объекта для работы с ним, для этого в стандарте определено понятие внутренних слотов, которые обозначены как имя в квадратных скобках [[SlotName]] . Для прототипов отведен приватный слот [[Prototype]] содержащий ссылку на объект-прототип (либо null , если прототипа нет).
Из-за того, что [[Prototype]] предназначался исключительно для самого JS движка, получить доступ к прототипу объекта было невозможно. Для случаев когда это было нужно, ввели нестандартное свойство __proto__ , которое поддержали многие браузеры и которое по итогу попало в сам стандарт, но как опциональное и стандартизированное только для обратной совместимости с существующим JS кодом.
О чем вам недоговаривает дебаггер, или он вам не прототип
Свойство __proto__ является геттером и сеттером для внутреннего слота [[Prototype]] и находится в Object.prototype :
Из-за этого я избегал записи __proto__ для обозначения прототипа. __proto__ находится не в самом объекте, что приводит к неожиданным результатам. Для демонстрации попробуем через __proto__ удалить прототип объекта и затем восстановить его:
Как так получилось? Дело в том, что __proto__ – это унаследованное свойство Object.prototype , а не самого объекта foo . Из-за этого в момент когда в цепочке прототипов пропадает ссылка на Object.prototype , __proto__ превращается в тыкву и перестает работать с прототипом.
А теперь отработаем кликбейт из введения. Представим следующую цепочку прототипов:В консоли Chrome foo будет выглядеть следующим образом:
А теперь уберем связь между baz и Object.prototype :
И теперь в консоли Chrome видим следующий результат:
Связь с Object.prototype разорвана у baz и __proto__ возвращает undefined даже у дочернего объекта foo , однако Chrome все равно показывает что __proto__ есть. Скорее всего тут имеется в виду внутренний слот [[Prototype]] , но для простоты это было изменено на __proto__ , ведь если не извращаться с цепочкой прототипов, это будет верно.
Как работать с прототипом объекта
Рассмотрим основные способы работы с прототипом: изменение прототипа и создание нового объекта с указанным прототипом.
Для изменения прототипа у существующего объекта есть всего два метода: использование сеттера __proto__ и метод Object.setPrototypeOf .
Если браузер не поддерживает ни один из этих методов, то изменить прототип объекта невозможно, можно только создать его копию с новым прототипом.
Но есть один нюанс с внутренним слотом [[Extensible]] который указывает на то, возможно ли добавлять к нему новые поля и менять его прототип. Есть несколько функций, которые выставляют этот флаг в false и предотвращают смену прототипа: Object.freeze , Object.seal , Object.preventExtensions . Пример:А теперь менее категоричный вопрос создания нового объекта с прототипом. Для этого есть следующие способы.
Стандартный способ:Если нет поддержки Object.create , но есть __proto__ :
И в случае если отсутствует поддержка всего вышеперечисленного:
Способ основан на логике работы оператора new , о которой поговорим чуть ниже. Но сам способ основан на том, что оператор new берет свойство prototype функции и использует его в качестве прототипа, т.е. устанавливает объект в [[Prototype]] , что нам и нужно.
Функции и конструкторы
А теперь поговорим про функции и как они работают в качестве конструкторов.
Функция Person тут является конструктором и создает два поля в новом объекте, а цепочка прототипов выглядит так:
Откуда взялся Person.prototype ? При объявлении функции, у нее автоматически создается свойство prototype для того чтобы ее можно было использовать как конструктор (note 3), таким образом свойство prototype функции не имеет отношения к прототипу самой функции, а задает прототипы для дочерних объектов. Это позволит реализовывать наследование и добавлять новые методы, например так:
И теперь вызов user.fullName() вернет строку "John Doe".
Что такое new
На самом деле оператор new не таит в себе никакой магии. При вызове new выполняет несколько действий:
- Создает новый объект self
- Записывает свойство prototype функции конструктора в прототип объекта self
- Вызывает функцию конструктор с объектом self в качестве аргумента this
- Возвращает self если конструктор вернул примитивное значение, иначе возвращает значение из конструктора
Все эти действия можно сделать силами самого языка, поэтому можно написать свой собственный оператор new в виде функции:
Но начиная с ES6 волшебство пришло и к new в виде свойства new.target, которое позволяет определить, была ли вызвана функция как конструктор с new, или как обычная функция:
new.target будет undefined для обычного вызова функции, и ссылкой на саму функцию в случае вызова через new ;
Наследование
Зная все вышеперечисленное, можно сделать классическое наследование дочернего класса Student от класса Person . Для этого нужно
- Создать конструктор Student с вызовом логики конструктора Person
- Задать объекту `Student.prototype` прототип от `Person`
- Добавить новые методы к `Student.prototype`
Фиолетовым цветом обозначены поля объекта (они все находятся в самом объекте, т.к. this у всей цепочки прототипов один), а методы желтым (находятся в прототипах соответствующих функций)
Вариант 1 предпочтительнее, т.к. Object.setPrototypeOf может привести к проблемам с производительностью.Сколько вам сахара к классу
Для того чтобы облегчить классическую схему наследование и предоставить более привычный синтаксис, были представлены классы, просто сравним код с примерами Person и Student:
Уменьшился не только бойлерплейт, но и поддерживаемость:
- В отличие от функции конструктора, при вызове конструктора без new выпадет ошибка
- Родительский класс указывается ровно один раз при объявлении
При этом цепочка прототипов получается идентичной примеру с явным указанием prototype у функций конструкторов.
Наивно было бы ожидать, что одна статья ответит на все вопросы. Если у Вас есть интересные вопросы, экскурсы в историю, аргументированные или беспочвенные заявления о том, что я сделал все не так, либо правки по ошибкам, пишите в комментарии.
P. P. S.
К сожалению главный кликбейт статьи перестал быть актуальным. В данный момент Chrome (версия 93, на момент обновления статьи) перестал использовать __proto__ для обозначения прототипа, и теперь отображает его как слот [[Prototype]] :
Справедливости ради хочу отметить что в Firefox (92) также не используется обозначение __proto__ :
Свойство F.prototype
Самым очевидным решением является назначение __proto__ в конструкторе.
Например, если я хочу, чтобы у всех объектов, которые создаются new Rabbit , был прототип animal , я могу сделать так:
Недостаток этого подхода – он не работает в IE10-.
К счастью, в JavaScript с древнейших времён существует альтернативный, встроенный в язык и полностью кросс-браузерный способ.
Чтобы новым объектам автоматически ставить прототип, конструктору ставится свойство prototype .
При создании объекта через new , в его прототип __proto__ записывается ссылка из prototype функции-конструктора.
Например, код ниже полностью аналогичен предыдущему, но работает всегда и везде:
Установка Rabbit.prototype = animal буквально говорит интерпретатору следующее: "При создании объекта через new Rabbit запиши ему __proto__ = animal ".
Свойство prototype имеет смысл только у конструктораСвойство с именем prototype можно указать на любом объекте, но особый смысл оно имеет, лишь если назначено функции-конструктору.
Само по себе, без вызова оператора new , оно вообще ничего не делает, его единственное назначение – указывать __proto__ для новых объектов.
Технически, в это свойство можно записать что угодно.
Однако, при работе new , свойство prototype будет использовано лишь в том случае, если это объект. Примитивное значение, такое как число или строка, будет проигнорировано.
Изменение встроенных прототипов
Встроенные прототипы можно изменять. Например, если добавить метод к String.prototype , метод становится доступен для всех строк:
В течение процесса разработки у нас могут возникнуть идеи о новых встроенных методах, которые нам хотелось бы иметь, и искушение добавить их во встроенные прототипы. Это плохая идея.
Прототипы глобальны, поэтому очень легко могут возникнуть конфликты. Если две библиотеки добавляют метод String.prototype.show , то одна из них перепишет метод другой.
Так что, в общем, изменение встроенных прототипов считается плохой идеей.
В современном программировании есть только один случай, в котором одобряется изменение встроенных прототипов. Это создание полифилов.
Полифил – это термин, который означает эмуляцию метода, который существует в спецификации JavaScript, но ещё не поддерживается текущим движком JavaScript.
Тогда мы можем реализовать его сами и добавить во встроенный прототип.
Итого
Для произвольной функции – назовём её Person , верно следующее:
Object.prototype
Давайте выведем пустой объект:
Где код, который генерирует строку "[object Object]" ? Это встроенный метод toString , но где он? obj ведь пуст!
…Но краткая нотация obj = <> – это то же самое, что и obj = new Object() , где Object – встроенная функция-конструктор для объектов с собственным свойством prototype , которое ссылается на огромный объект с методом toString и другими.
Вот что происходит:
Когда вызывается new Object() (или создаётся объект с помощью литерала <. >), свойство [[Prototype]] этого объекта устанавливается на Object.prototype по правилам, которые мы обсуждали в предыдущей главе:
Таким образом, когда вызывается obj.toString() , метод берётся из Object.prototype .
Мы можем проверить это так:
Обратите внимание, что по цепочке прототипов выше Object.prototype больше нет свойства [[Prototype]] :
Другие встроенные прототипы
Другие встроенные объекты, такие как Array , Date , Function и другие, также хранят свои методы в прототипах.
Например, при создании массива [1, 2, 3] внутренне используется конструктор массива Array . Поэтому прототипом массива становится Array.prototype , предоставляя ему свои методы. Это позволяет эффективно использовать память.
Согласно спецификации, наверху иерархии встроенных прототипов находится Object.prototype . Поэтому иногда говорят, что «всё наследует от объектов».
Вот более полная картина (для трёх встроенных объектов):
Давайте проверим прототипы:
Некоторые методы в прототипах могут пересекаться, например, у Array.prototype есть свой метод toString , который выводит элементы массива через запятую:
Как мы видели ранее, у Object.prototype есть свой метод toString , но так как Array.prototype ближе в цепочке прототипов, то берётся именно вариант для массивов:
В браузерных инструментах, таких как консоль разработчика, можно посмотреть цепочку наследования (возможно, потребуется использовать console.dir для встроенных объектов):
Другие встроенные объекты устроены аналогично. Даже функции – они объекты встроенного конструктора Function , и все их методы ( call / apply и другие) берутся из Function.prototype . Также у функций есть свой метод toString .
Читайте также: