Что такое prototype javascript

Встроенные прототипы

Свойство «prototype» широко используется внутри самого языка JavaScript. Все встроенные функции-конструкторы используют его.

Сначала мы рассмотрим детали, а затем используем «prototype» для добавления встроенным объектам новой функциональности.

Object.prototype

Давайте выведем пустой объект:

let obj = <>; alert( obj ); // "[object Object]" ?

Где код, который генерирует строку «[object Object]» ? Это встроенный метод toString , но где он? obj ведь пуст!

…Но краткая нотация obj = <> – это то же самое, что и obj = new Object() , где Object – встроенная функция-конструктор для объектов с собственным свойством prototype , которое ссылается на огромный объект с методом toString и другими.

Другие встроенные объекты устроены аналогично. Даже функции – они объекты встроенного конструктора Function , и все их методы ( call / apply и другие) берутся из Function.prototype . Также у функций есть свой метод toString .

function f() <> alert(f.__proto__ == Function.prototype); // true alert(f.__proto__.__proto__ == Object.prototype); // true, наследует от Object

Примитивы

Самое сложное происходит со строками, числами и булевыми значениями.

Как мы помним, они не объекты. Но если мы попытаемся получить доступ к их свойствам, то тогда будет создан временный объект-обёртка с использованием встроенных конструкторов String , Number и Boolean , который предоставит методы и после этого исчезнет.

Эти объекты создаются невидимо для нас, и большая часть движков оптимизирует этот процесс, но спецификация описывает это именно таким образом. Методы этих объектов также находятся в прототипах, доступных как String.prototype , Number.prototype и Boolean.prototype .

Специальные значения null и undefined стоят особняком. У них нет объектов-обёрток, так что методы и свойства им недоступны. Также у них нет соответствующих прототипов.

Изменение встроенных прототипов

Встроенные прототипы можно изменять. Например, если добавить метод к String.prototype , метод становится доступен для всех строк:

String.prototype.show = function() < alert(this); >; "BOOM!".show(); // BOOM!

В течение процесса разработки у нас могут возникнуть идеи о новых встроенных методах, которые нам хотелось бы иметь, и искушение добавить их во встроенные прототипы. Это плохая идея.

Прототипы глобальны, поэтому очень легко могут возникнуть конфликты. Если две библиотеки добавляют метод String.prototype.show , то одна из них перепишет метод другой.

Так что, в общем, изменение встроенных прототипов считается плохой идеей.

В современном программировании есть только один случай, в котором одобряется изменение встроенных прототипов. Это создание полифилов.

Полифил – это термин, который означает эмуляцию метода, который существует в спецификации JavaScript, но ещё не поддерживается текущим движком JavaScript.

Тогда мы можем реализовать его сами и добавить во встроенный прототип.

if (!String.prototype.repeat) < // Если такого метода нет // добавляем его в прототип String.prototype.repeat = function(n) < // повторить строку n раз // на самом деле код должен быть немного более сложным // (полный алгоритм можно найти в спецификации) // но даже неполный полифил зачастую достаточно хорош для использования return new Array(n + 1).join(this); >; > alert( "La".repeat(3) ); // LaLaLa

Заимствование у прототипов

В главе Декораторы и переадресация вызова, call/apply мы говорили о заимствовании методов.

Это когда мы берём метод из одного объекта и копируем его в другой.

Некоторые методы встроенных прототипов часто одалживают.

Например, если мы создаём объект, похожий на массив (псевдомассив), мы можем скопировать некоторые методы из Array в этот объект.

let obj = < 0: "Hello", 1: "world!", length: 2, >; obj.join = Array.prototype.join; alert( obj.join(',') ); // Hello,world!

Это работает, потому что для внутреннего алгоритма встроенного метода join важны только корректность индексов и свойство length , он не проверяет, является ли объект на самом деле массивом. И многие встроенные методы работают так же.

Альтернативная возможность – мы можем унаследовать от массива, установив obj.__proto__ как Array.prototype , таким образом все методы Array станут автоматически доступны в obj .

Но это будет невозможно, если obj уже наследует от другого объекта. Помните, мы можем наследовать только от одного объекта одновременно.

Заимствование методов – гибкий способ, позволяющий смешивать функциональность разных объектов по необходимости.

Итого

  • Все встроенные объекты следуют одному шаблону:
    • Методы хранятся в прототипах ( Array.prototype , Object.prototype , Date.prototype и т.д.).
    • Сами объекты хранят только данные (элементы массивов, свойства объектов, даты).

    Задачи

    Добавить функциям метод «f.defer(ms)»

    Добавьте всем функциям в прототип метод defer(ms) , который вызывает функции через ms миллисекунд.

    После этого должен работать такой код:

    function f() < alert("Hello!"); >f.defer(1000); // выведет "Hello!" через 1 секунду

    Источник

    Прототип объекта

    Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

    Более новая информация по этой теме находится на странице https://learn.javascript.ru/prototype-inheritance.

    Объекты в JavaScript можно организовать в цепочки так, чтобы свойство, не найденное в одном объекте, автоматически искалось бы в другом.

    Связующим звеном выступает специальное свойство __proto__ .

    Прототип proto

    Если один объект имеет специальную ссылку __proto__ на другой объект, то при чтении свойства из него, если свойство отсутствует в самом объекте, оно ищется в объекте __proto__ .

    Свойство __proto__ доступно во всех браузерах, кроме IE10-, а в более старых IE оно, конечно же, тоже есть, но напрямую к нему не обратиться, требуются чуть более сложные способы, которые мы рассмотрим позднее.

    var animal = < eats: true >; var rabbit = < jumps: true >; rabbit.__proto__ = animal; // в rabbit можно найти оба свойства alert( rabbit.jumps ); // true alert( rabbit.eats ); // true
    1. Первый alert здесь работает очевидным образом – он выводит свойство jumps объекта rabbit .
    2. Второй alert хочет вывести rabbit.eats , ищет его в самом объекте rabbit , не находит – и продолжает поиск в объекте rabbit.__proto__ , то есть, в данном случае, в animal .

    Иллюстрация происходящего при чтении rabbit.eats (поиск идёт снизу вверх):

    Объект, на который указывает ссылка __proto__ , называется «прототипом». В данном случае получилось, что animal является прототипом для rabbit .

    Также говорят, что объект rabbit «прототипно наследует» от animal .

    Обратим внимание – прототип используется исключительно при чтении. Запись значения, например, rabbit.eats = value или удаление delete rabbit.eats – работает напрямую с объектом.

    В примере ниже мы записываем свойство в сам rabbit , после чего alert перестаёт брать его у прототипа, а берёт уже из самого объекта:

    var animal = < eats: true >; var rabbit = < jumps: true, eats: false >; rabbit.__proto__ = animal; alert( rabbit.eats ); // false, свойство взято из rabbit

    Другими словами, прототип – это «резервное хранилище свойств и методов» объекта, автоматически используемое при поиске.

    У объекта, который является __proto__ , может быть свой __proto__ , у того – свой, и так далее. При этом свойства будут искаться по цепочке.

    Если вы будете читать спецификацию ECMAScript – свойство __proto__ обозначено в ней как [[Prototype]] .

    Двойные квадратные скобки здесь важны, чтобы не перепутать его с совсем другим свойством, которое называется prototype , и которое мы рассмотрим позже.

    Метод hasOwnProperty

    Обычный цикл for..in не делает различия между свойствами объекта и его прототипа.

    Он перебирает всё, например:

    var animal = < eats: true >; var rabbit = < jumps: true, __proto__: animal >; for (var key in rabbit) < alert( key + " = " + rabbitЧто такое prototype javascript ); // выводит и "eats" и "jumps" >

    Иногда хочется посмотреть, что находится именно в самом объекте, а не в прототипе.

    Вызов obj.hasOwnProperty(prop) возвращает true , если свойство prop принадлежит самому объекту obj , иначе false .

    var animal = < eats: true >; var rabbit = < jumps: true, __proto__: animal >; alert( rabbit.hasOwnProperty('jumps') ); // true: jumps принадлежит rabbit alert( rabbit.hasOwnProperty('eats') ); // false: eats не принадлежит

    Для того, чтобы перебрать свойства самого объекта, достаточно профильтровать key через hasOwnProperty :

    var animal = < eats: true >; var rabbit = < jumps: true, __proto__: animal >; for (var key in rabbit) < if (!rabbit.hasOwnProperty(key)) continue; // пропустить "не свои" свойства alert( key + " = " + rabbitЧто такое prototype javascript ); // выводит только "jumps" >

    Object.create(null)

    Зачастую объекты используют для хранения произвольных значений по ключу, как коллекцию:

    var data = <>; data.text = "Привет"; data.age = 35; // . 

    При дальнейшем поиске в этой коллекции мы найдём не только text и age , но и встроенные функции:

    var data = <>; alert(data.toString); // функция, хотя мы её туда не записывали

    Это может быть неприятным сюрпризом и приводить к ошибкам, если названия свойств приходят от посетителя и могут быть произвольными.

    Чтобы этого избежать, мы можем исключать свойства, не принадлежащие самому объекту:

    var data = <>; // выведет toString только если оно записано в сам объект alert(data.hasOwnProperty('toString') ? data.toString : undefined);
    var data = Object.create(null); data.text = "Привет"; alert(data.text); // Привет alert(data.toString); // undefined

    Объект, создаваемый при помощи Object.create(null) не имеет прототипа, а значит в нём нет лишних свойств. Для коллекции – как раз то, что надо.

    Методы для работы с proto

    В современных браузерах есть два дополнительных метода для работы с __proto__ . Зачем они нужны, если есть __proto__ ? В общем-то, не очень нужны, но по историческим причинам тоже существуют.

    Чтение: Object.getPrototypeOf(obj) Возвращает obj.__proto__ (кроме IE8-) Запись: Object.setPrototypeOf(obj, proto) Устанавливает obj.__proto__ = proto (кроме IE10-).

    Кроме того, есть ещё один вспомогательный метод:

    Создание объекта с прототипом: Object.create(proto, descriptors) Создаёт пустой объект с __proto__ , равным первому аргументу (кроме IE8-), второй необязательный аргумент может содержать дескрипторы свойств.

    Итого

    • В JavaScript есть встроенное «наследование» между объектами при помощи специального свойства __proto__ .
    • При установке свойства rabbit.__proto__ = animal говорят, что объект animal будет «прототипом» rabbit .
    • При чтении свойства из объекта, если его в нём нет, оно ищется в __proto__ . Прототип задействуется только при чтении свойства. Операции присвоения obj.prop = или удаления delete obj.prop совершаются всегда над самим объектом obj .

    Несколько прототипов одному объекту присвоить нельзя, но можно организовать объекты в цепочку, когда один объект ссылается на другой при помощи __proto__ , тот ссылается на третий, и так далее.

    В современных браузерах есть методы для работы с прототипом:

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

    Также мы рассмотрим, как свойство __proto__ используется внутри самого языка JavaScript и как организовать классы с его помощью.

    Источник

    Читайте также:  Python string methods find
Оцените статью