Все встроенные объекты JavaScript имеют прототип.
Прототип Object.prototype
Для примера, создадим пустой объект. В нём ничего нет… Но откуда же берется метод toString?
var obj = { };
alert( obj.toString() ); // кто возвратил строку "[object Object]" ?
Никакой магии здесь нет. Запись obj = {} является краткой формой obj = new Object, где Object — встроенная функция-конструктор для объектов.
При создании объекта через new, функция-конструктор Object ставит ему obj.__proto__ = Object.prototype, где Object.prototype — встроенный объект, хранящий свойства и методы, общие для объектов, в частности, Object.prototype.toString.

Таким образом получается, что все свойства Object.prototype, включая toString, доступны для любых объектов.
То же самое происходит с массивами Array, функциями Function и с другими объектами. Встроенные методы для них находятся в Array.prototype, Function.prototype и т.п.

Как видно, получается иерархия наследования, которая всегда заканчивается на Object.prototype. Объект Object.prototype — единственный, у которого __proto__ равно null.
Поэтому говорят, что «все объекты наследуют от Object». На самом деле ничего подобного. Это все прототипы наследуют от Object.prototype.
Прототип является хранилищем методов.
Автопреобразование примитивов
В JavaScript существуют встроенные функции-конструкторы String, Number, Boolean. Но их не принято использовать для создания через new, т.к. при этом создаются объектные значения, что может поломать код:
alert(typeof 1); // "number" alert(typeof new Number(1)); // "object" ?!?
Несмотря на то, что в явном виде объекты String, Number, Boolean не создаются, их прототипы всё же используются. Они служат хранилищами методов для строк, чисел, булевых значений.
- Примитивное значение, такое как
"строка", при вызове метода, например,"строка".slice(1), неявно преобразуется в соответствующий объектString. - Затем ищется и вызывается метод прототипа
String.prototype.slice. - Результатом вызова
sliceявляется снова примитив.
Конечно же, браузеры применяют оптимизации и стараются не создавать лишних объектов, но прототип они используют как описано выше. А значит, методы для строк, чисел, булевых значений можно изменять и расширять…
Изменение встроенных прототипов
Встроенные прототипы можно изменять. Добавлять свои методы.
Мы можем написать метод для многократного повторения строки, и он тут же станет доступным для всех строк:
String.prototype.repeat = function(times) {
return new Array(times+1).join(this);
}
alert( "ля".repeat(3) ) // ляляля
Аналогично мы могли бы создать метод Object.prototype.each(func), который будет применять func к каждому свойству:
Object.prototype.each = function(f) {
for (var prop in this) {
var value = this[prop];
f.call(value, prop, value); // вызовет f(prop, value), this=value
}
}
// Попробуем! (работает неверно!)
var obj = { name: 'Вася', age: 25 };
obj.each(function(prop, val) {
alert(prop); // name -> age -> (!) each
})
Обратите внимание — пример выше работает неправильно. Он выводит лишнее свойство each, т.к. цикл for..in перебирает свойства в прототипе. Встроенные методы при этом пропускаются, а наш метод — вылез.
В данном случае это легко поправить добавлением проверки hasOwnProperty:
Object.prototype.each = function(f) {
for (var prop in this) {
*!*
if (!this.hasOwnProperty(prop)) continue;
*/!*
var value = this[prop];
f.call(value, prop, value);
}
}
// Теперь все будет в порядке
var obj = { name: 'Вася', age: 25 };
obj.each(function(prop, val) {
alert(prop); // name -> age
})
В данном случае это сработало, теперь код верен. Но мы же не хотим добавлять hasOwnProperty в цикл по любому объекту…
Свойства, добавленные в Object.prototype, появятся по всех for..in циклах. Они в них будут лишними. Не добавляйте свойства в Object.prototype.
for..inВстроенные свойства и методы не перебираются в for..in, так как у них есть специальный внутренний флаг [[Enumerable]], установленный в false.
Современные браузеры (IE с версии 9) позволяют устанавливать этот флаг для новых свойств, которые в результате также будут пропускаться for..in.
Есть объекты, которые не участвуют в циклах for..in, например строки, функции.. И в их прототипы, пожалуй, можно добавлять свои методы. Но здесь есть свои «за» и «против»:
- Новые методы позволяют писать более короткий и ясный код.
- Новые свойства могут конфликтовать между собой. Представьте, что вы подключили две библиотеки, которые добавили одно и то же свойство в прототип, но определили его по-разному. Конфликт неизбежен.
- Встроенные прототипы влияют глобально на весь код, и менять их не очень хорошо с архитектурной точки зрения.
Допустимо изменение прототипа и встроенных объектов, которое добавляет поддержку метода из современных стандартов в те браузеры, где её пока нет.
Например, добавим Object.create(proto) в старые браузеры:
if (!Object.create) {
Object.create = function(proto) {
function F() {}
F.prototype = proto;
return new F;
}
}
Существует даже библиотека es5-shim, которая предоставляет многие функции современного JavaScript для старых браузеров. Они добавляются во встроенные объекты и их прототипы.
Итого
- Методы встроенных объектов хранятся в их прототипах.
- Встроенные прототипы можно расширить или поменять.
- Добавление методов в
Object.prototypeломает циклыfor..in. Другие прототипы изменять не настолько опасно, но все же не рекомендуется во избежание конфликтов.Отдельно стоит изменение с целью добавления современных методов в старые браузеры, таких как Object.create, Object.keys, Function.prototype.bind и т.п. Это допустимо.
Комментарии
- Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
- Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
- Комментарии без смысла, с рекламой или не о статье вообще - удаляются.