Объявлена функция function f чем является f prototype

Обновлено: 06.07.2024

Свойство "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 выполняет несколько действий:

  1. Создает новый объект self
  2. Записывает свойство prototype функции конструктора в прототип объекта self
  3. Вызывает функцию конструктор с объектом self в качестве аргумента this
  4. Возвращает self если конструктор вернул примитивное значение, иначе возвращает значение из конструктора

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

Но начиная с ES6 волшебство пришло и к new в виде свойства new.target, которое позволяет определить, была ли вызвана функция как конструктор с new, или как обычная функция:

new.target будет undefined для обычного вызова функции, и ссылкой на саму функцию в случае вызова через new ;

Наследование

Зная все вышеперечисленное, можно сделать классическое наследование дочернего класса Student от класса Person . Для этого нужно

  1. Создать конструктор Student с вызовом логики конструктора Person
  2. Задать объекту `Student.prototype` прототип от `Person`
  3. Добавить новые методы к `Student.prototype`



Фиолетовым цветом обозначены поля объекта (они все находятся в самом объекте, т.к. this у всей цепочки прототипов один), а методы желтым (находятся в прототипах соответствующих функций)
Вариант 1 предпочтительнее, т.к. Object.setPrototypeOf может привести к проблемам с производительностью.

Сколько вам сахара к классу

Для того чтобы облегчить классическую схему наследование и предоставить более привычный синтаксис, были представлены классы, просто сравним код с примерами Person и Student:

Уменьшился не только бойлерплейт, но и поддерживаемость:

  • В отличие от функции конструктора, при вызове конструктора без new выпадет ошибка
  • Родительский класс указывается ровно один раз при объявлении

При этом цепочка прототипов получается идентичной примеру с явным указанием prototype у функций конструкторов.

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

P. P. S.

К сожалению главный кликбейт статьи перестал быть актуальным. В данный момент Chrome (версия 93, на момент обновления статьи) перестал использовать __proto__ для обозначения прототипа, и теперь отображает его как слот [[Prototype]] :

image

Справедливости ради хочу отметить что в 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 и вы, наверняка, сами поймёте, как она работает…

    Если где-то неясности, то её построчное описание:

    1. Создана новая функция F . Она ничего не делает с this , так что если вызвать new F , то получим пустой объект.
    2. Свойство F.prototype устанавливается в будущий прототип proto
    3. Результатом вызова new F будет пустой объект с __proto__ равным значению F.prototype .
    4. Мы получили пустой объект с заданным прототипом, как и хотели. Возвратим его.

    Для унификации можно запустить такой код, и метод 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 выполняет несколько действий:

    1. Создает новый объект self
    2. Записывает свойство prototype функции конструктора в прототип объекта self
    3. Вызывает функцию конструктор с объектом self в качестве аргумента this
    4. Возвращает self если конструктор вернул примитивное значение, иначе возвращает значение из конструктора

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

    Но начиная с ES6 волшебство пришло и к new в виде свойства new.target, которое позволяет определить, была ли вызвана функция как конструктор с new, или как обычная функция:

    new.target будет undefined для обычного вызова функции, и ссылкой на саму функцию в случае вызова через new ;

    Наследование

    Зная все вышеперечисленное, можно сделать классическое наследование дочернего класса Student от класса Person . Для этого нужно

    1. Создать конструктор Student с вызовом логики конструктора Person
    2. Задать объекту `Student.prototype` прототип от `Person`
    3. Добавить новые методы к `Student.prototype`



    Фиолетовым цветом обозначены поля объекта (они все находятся в самом объекте, т.к. this у всей цепочки прототипов один), а методы желтым (находятся в прототипах соответствующих функций)
    Вариант 1 предпочтительнее, т.к. Object.setPrototypeOf может привести к проблемам с производительностью.

    Сколько вам сахара к классу

    Для того чтобы облегчить классическую схему наследование и предоставить более привычный синтаксис, были представлены классы, просто сравним код с примерами Person и Student:

    Уменьшился не только бойлерплейт, но и поддерживаемость:

    • В отличие от функции конструктора, при вызове конструктора без new выпадет ошибка
    • Родительский класс указывается ровно один раз при объявлении

    При этом цепочка прототипов получается идентичной примеру с явным указанием prototype у функций конструкторов.

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

    P. P. S.

    К сожалению главный кликбейт статьи перестал быть актуальным. В данный момент Chrome (версия 93, на момент обновления статьи) перестал использовать __proto__ для обозначения прототипа, и теперь отображает его как слот [[Prototype]] :

    image

    Справедливости ради хочу отметить что в 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 .

    Читайте также: