Объекты в 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__
, у того – свой, и так далее. При этом свойства будут искаться по цепочке.
Если вы будете читать спецификацию 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 и как организовать классы с его помощью.