Встроенные прототипы
Свойство «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 не имеют объектов-обёрток
Специальные значения 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)»
важность: 5
Добавьте всем функциям в прототип метод 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 оно, конечно же, тоже есть, но напрямую к нему не обратиться, требуются чуть более сложные способы, которые мы рассмотрим позднее.
Пример кода (кроме IE10-):
var animal = < eats: true >; var rabbit = < jumps: true >; rabbit.__proto__ = animal; // в rabbit можно найти оба свойства alert( rabbit.jumps ); // true alert( rabbit.eats ); // true- Первый alert здесь работает очевидным образом – он выводит свойство jumps объекта rabbit .
- Второй 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__ , у того – свой, и так далее. При этом свойства будут искаться по цепочке.
Ссылка 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[key] ); // выводит и "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[key] ); // выводит только "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__ , тот ссылается на третий, и так далее.
В современных браузерах есть методы для работы с прототипом:
- Object.getPrototypeOf(obj) (кроме IE8-)
- Object.setPrototypeOf(obj, proto) (кроме IE10-)
- Object.create(proto, descriptors) (кроме IE8-)
Возможно, вас смущает недостаточная поддержка __proto__ в старых IE. Но это не страшно. В последующих главах мы рассмотрим дополнительные методы работы с __proto__ , включая те, которые работают везде.
Также мы рассмотрим, как свойство __proto__ используется внутри самого языка JavaScript и как организовать классы с его помощью.
Зачем нужен prototype?
Зачем существует прототипирование ? Почему нельзя просто добавлять в объект новые функции obj.newFunc = function()<> , а затем использовать их ? Разве это не то же самое что и obj.prototype.newFunc = function() <> ? Объясните в чём разница и зачем нужен prototype ?
Отслеживать
задан 13 авг 2015 в 15:14
Станислав Далинин Станислав Далинин
377 1 1 золотой знак 9 9 серебряных знаков 20 20 бронзовых знаковКак правило, свойство prototype используется для предоставления базового набора функциональных возможностей классу объектов. Новые экземпляры объекта «наследуют» поведение прототипа, присвоенного этому объекту. примеры тут
13 авг 2015 в 15:17
Всё равно плохо доходит.
13 авг 2015 в 15:232 ответа 2
Сортировка: Сброс на вариант по умолчанию
Для того, что бы понять, зачем нужен prototype , надо понять, что такое классы и зачем они нужны: Пример ‘из учебника’
function Animal (name) < this.name = name >var dog = new Animal("Sharky");Что произошло? Мы создали новый объект dog , у которого есть свойство name , сделали мы это с помощью функции-конструктора Animal .
В данном случае Animal — класс, dog — экземплярТеперь к prototype , допустим, мы хотим, что бы у всех экземпляров класс Animal был метод walk() . Добавлять этот метод к каждому экземпляру после создания, ни какого редактора не хватит, поэтому нам и нужен prototype .
Вкратце — в прототипе ( prototype ) содержатся все унаследованные методы/свойства, которое нам выдает класс при созданииfunction Animal (name) < this.name = name; // для каждого экземпляра свое имя >Animal.prototype.walk = function () < // общая для всех экземпляров alert("I can walk"); >var dog = new Animal("dog"); var cat = new Animal("cat"); alert(dog.name); // dog alert(cat.name); // cat // и dog, и cat имеют метод walk dog.walk(); cat.walk(); // I can walkPrototype — это хранилище общих методов и свойств для всех экземпляров одного класса. Что бы лучше понять, почитай про ООП в js( тут например )
Когда использовать методы в конструкторе, а когда — в прототипе?
Я не понимаю, в каких случаях необходимо метод объекта объявлять в самом конструкторе, а когда — в прототипе.
Вот например в коде ниже я не вижу разницы между двумя методами и хотелось бы чтобы кто-то пролил свет на их использование.
function A() < this.method = function() < alert('Метод конструктора'); >> A.prototype.method2 = function() < alert('Метод прототипа'); >let object = new A();Известно ведь, что в новом стандарте ES6 с синтаксисом классов и вовсе нет методов в конструкторе.
- Вопрос задан более трёх лет назад
- 1223 просмотра
2 комментария
Простой 2 комментария

«в новом стандарте ES6 с синтаксисом классов и вовсе нет методов в конструкторе» — конечно есть.
«я не вижу разницы между двумя методами» — разница в том, что один объявлен как свойство экземпляра, а другой — как свойство прототипа, методы из которого наследуются экземплярами. Технически, это исчерпывающий список различий. Остальное — последствия, например хранение метода в каждом экземпляре приведет к увеличенному расходу памяти и т.д.
«Когда использовать методы в конструкторе, а когда — в прототипе?» — и в том и в другом случае ответ один: когда это нужно. Если не видите нужды в чем-то, то не делайте этого; все просто. Нужно забить память объявлением однотипных функций? Объявляйте методы в конструкторе. Не нужно? Объявляйте в прототипе.
Хорошего примера объявления методов в конструкторе, к сожалению, не могу привести.