Свойство "prototype"
широко используется ядром самого JavaScript. Его используют все встроенные функции-конструкторы.
Сначала мы рассмотрим детали, а затем то, как использовать его для добавления новых возможностей к встроенным объектам.
Допустим, мы выводим пустой объект:
пусть объект = {}; предупреждение (объект); // "[объект Объект]" ?
Где код, который генерирует строку "[object Object]"
? Это встроенный метод toString
, но где он? obj
пуст!
…Но краткая запись obj = {}
такая же, как obj = new Object()
, где Object
— это встроенная функция-конструктор объекта с собственным prototype
, ссылающимся на огромный объект с помощью toString
и других методов.
Вот что происходит:
Когда вызывается new Object()
(или создается буквальный объект {...}
), его [[Prototype]]
устанавливается в Object.prototype
в соответствии с правилом, которое мы обсуждали в предыдущей главе:
Итак, когда вызывается obj.toString()
метод берется из Object.prototype
.
Мы можем проверить это следующим образом:
пусть объект = {}; alert(obj.__proto__ === Object.prototype); // истинный alert(obj.toString === obj.__proto__.toString); //истинный alert(obj.toString === Object.prototype.toString); //истинный
Обратите внимание, что в цепочке выше Object.prototype
больше нет [[Prototype]]
:
оповещение(Object.prototype.__proto__); // нулевой
Другие встроенные объекты, такие как Array
, Date
, Function
и другие, также содержат методы в прототипах.
Например, когда мы создаем массив [1, 2, 3]
, внутри используется конструктор new Array()
по умолчанию. Таким образом, Array.prototype
становится его прототипом и предоставляет методы. Это очень эффективно с точки зрения использования памяти.
По спецификации все встроенные прототипы имеют Object.prototype
вверху. Вот почему некоторые говорят, что «все наследуется от объектов».
Вот общая картина (для 3 встроенных модулей):
Проверим прототипы вручную:
пусть arr = [1, 2, 3]; // он наследуется от Array.prototype? alert( arr.__proto__ === Array.prototype ); // истинный // затем из Object.prototype? alert( arr.__proto__.__proto__ === Object.prototype ); // истинный // и ноль сверху. alert( arr.__proto__.__proto__.__proto__ ); // нулевой
Некоторые методы в прототипах могут перекрываться, например, Array.prototype
имеет собственную toString
, в которой перечислены элементы, разделенные запятыми:
пусть arr = [1, 2, 3] оповещение (прибытие); // 1,2,3 <- результат Array.prototype.toString
Как мы видели ранее, Object.prototype
также имеет toString
, но Array.prototype
находится ближе в цепочке, поэтому используется вариант массива.
Инструменты в браузере, такие как консоль разработчика Chrome, также отображают наследование (возможно, потребуется использовать console.dir
для встроенных объектов):
Другие встроенные объекты работают аналогичным образом. Даже функции — это объекты встроенного конструктора Function
, а их методы ( call
/ apply
и другие) взяты из Function.prototype
. Функции также имеют свою собственную toString
.
функция е() {} alert(f.__proto__ == Function.prototype); // истинный alert(f.__proto__.__proto__ == Object.prototype); // правда, наследуем от объектов
Самое сложное происходит со строками, числами и логическими значениями.
Как мы помним, они не являются объектами. Но если мы попытаемся получить доступ к их свойствам, временные объекты-обертки будут созданы с помощью встроенных конструкторов String
, Number
и Boolean
. Они предоставляют методы и исчезают.
Эти объекты создаются незаметно для нас, и большинство движков оптимизируют их, но спецификация описывает это именно так. Методы этих объектов также находятся в прототипах, доступных как String.prototype
, Number.prototype
и Boolean.prototype
.
Значения null
и undefined
не имеют оболочек объектов.
Особняком стоят специальные значения null
и undefined
. У них нет оберток объектов, поэтому для них недоступны методы и свойства. И соответствующих прототипов тоже нет.
Нативные прототипы можно модифицировать. Например, если мы добавим метод в String.prototype
, он станет доступен для всех строк:
String.prototype.show = функция() { предупреждение (это); }; «БУМ!».show(); // БУМ!
В процессе разработки у нас могут возникнуть идеи относительно новых встроенных методов, которые мы хотели бы иметь, и у нас может возникнуть соблазн добавить их в собственные прототипы. Но это вообще плохая идея.
Важный:
Прототипы глобальны, поэтому легко получить конфликт. Если две библиотеки добавят метод String.prototype.show
, то одна из них перезапишет метод другой.
Итак, в целом модификация нативного прототипа считается плохой идеей.
В современном программировании есть только один случай, когда допускается изменение собственных прототипов. Это полифиллинг.
Полифиллинг — это термин, обозначающий замену метода, который существует в спецификации JavaScript, но еще не поддерживается конкретным движком JavaScript.
Затем мы можем реализовать его вручную и заполнить им встроенный прототип.
Например:
if (!String.prototype.repeat) { // если такого метода нет // добавляем его в прототип String.prototype.repeat = функция (n) { // повторяем строку n раз // на самом деле код должен быть немного сложнее // (полный алгоритм есть в спецификации) // но даже несовершенный полифил часто считается достаточно хорошим вернуть новый массив (n + 1).join(this); }; } alert( "La".repeat(3) ); // ЛаЛаЛа
В главе Декораторы и переадресация, вызов/применение мы говорили о заимствовании методов.
Это когда мы берем метод из одного объекта и копируем его в другой.
Некоторые методы из нативных прототипов часто заимствуются.
Например, если мы создаем объект, похожий на массив, мы можем захотеть скопировать в него некоторые методы Array
.
Например
пусть объект = { 0: «Привет», 1: «мир!», длина: 2, }; obj.join = Array.prototype.join; предупреждение(obj.join(',')); // Привет, мир!
Это работает, потому что внутренний алгоритм встроенного метода join
заботится только о правильных индексах и свойстве length
. Он не проверяет, действительно ли объект является массивом. Многие встроенные методы аналогичны этому.
Другая возможность — наследовать, установив obj.__proto__
значение Array.prototype
, чтобы все методы Array
автоматически были доступны в obj
.
Но это невозможно, если obj
уже наследуется от другого объекта. Помните, что мы можем наследовать только от одного объекта одновременно.
Заимствование методов является гибким, оно позволяет при необходимости смешивать функциональные возможности разных объектов.
Все встроенные объекты следуют одному и тому же шаблону:
Методы хранятся в прототипе ( Array.prototype
, Object.prototype
, Date.prototype
и т. д.).
Сам объект хранит только данные (элементы массива, свойства объекта, дату).
Примитивы также хранят методы в прототипах объектов-оболочек: Number.prototype
, String.prototype
и Boolean.prototype
. Только undefined
и null
не имеют объектов-оберток.
Встроенные прототипы можно модифицировать или наполнять новыми методами. Но менять их не рекомендуется. Единственный допустимый случай, вероятно, когда мы добавляем новый стандарт, но он еще не поддерживается движком JavaScript.
важность: 5
Добавьте к прототипу всех функций метод defer(ms)
, который запускает функцию через ms
миллисекундах.
После этого такой код должен работать:
функция е() { Предупреждение("Привет!"); } f.defer(1000); // показывает "Привет!" через 1 секунду
Function.prototype.defer = функция(мс) { setTimeout (это, мс); }; функция е() { Предупреждение("Привет!"); } f.defer(1000); // показывает "Привет!" через 1 секунду
важность: 4
Добавьте к прототипу всех функций метод defer(ms)
, который возвращает обертку, задерживающую вызов ms
миллисекунды.
Вот пример того, как это должно работать:
функция f(a, b) { предупреждение (а + б); } f.defer(1000)(1, 2); // показывает 3 через 1 секунду
Обратите внимание, что аргументы должны передаваться исходной функции.
Function.prototype.defer = функция(мс) { пусть f = это; возвращаемая функция(...args) { setTimeout(() => f.apply(this, args), мс); } }; // проверяем это функция f(a, b) { предупреждение (а + б); } f.defer(1000)(1, 2); // показывает 3 через 1 секунду
Обратите внимание: мы используем this
в f.apply
, чтобы украшение работало для методов объекта.
Таким образом, если функция-обертка вызывается как метод объекта, this
передается исходному методу f
.
Function.prototype.defer = функция(мс) { пусть f = это; возвращаемая функция(...args) { setTimeout(() => f.apply(this, args), мс); } }; пусть пользователь = { имя: «Джон», сказатьПривет() { оповещение(это.имя); } } user.sayHi = user.sayHi.defer(1000); пользователь.sayHi();