Объекты в 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 и как организовать классы с его помощью.
Комментарии
<code>, для нескольких строк кода — тег<pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)