Предупреждение: Изменение прототипа [[Prototype]]
объекта является, по самой природе оптимизации доступа к свойствам в современных движках JavaScript, очень медленной операцией, это справедливо для любого браузера и движка JavaScript. Изменение прототипов очень тонко и обширно влияет на производительность, причём это влияние не ограничивается просто временем для операции присваивания obj.__proto__ = ...
, оно может распространяться на любой код, который имеет доступ к любому объекту, чей прототип [[Prototype]]
был изменён. Если вы заботитесь о производительности, вы никогда не должны изменять прототип [[Prototype]]
объекта. Вместо этого создайте объект с нужным прототипом [[Prototype]]
, с помощью метода Object.create()
.
Предупреждение: хотя на сегодняшний момент большинство браузеров поддерживают свойство Object.prototype.__proto__
, его поведение только недавно было стандартизировано в новой спецификации ECMAScript 6. Если вам требуется поддержка браузеров до этой спецификации, рекомендуется использовать вместо него метод Object.getPrototypeOf()
.
Сводка
Свойство __proto__
объекта Object.prototype
является свойством доступа (комбинацией геттера и сеттера), которые расширяют внутренний прототип [[Prototype]]
объекта (являющийся объектом или null
), через который осуществлялся доступ.
Использование свойства __proto__
вызывает споры и многих оно разочаровало. Ранее оно никогда не включалось в спецификацию EcmaScript, но современные браузеры всё равно решили его реализовать. Сегодня свойство __proto__
стандартизировано в спецификации ECMAScript 6 и будет поддерживаться в будущем. Тем не менее, изменение прототипа [[Prototype]]
объекта всё ещё остаётся медленной операцией, которую следует избегать, если вы беспокоитесь о производительности.
Свойство __proto__
также может использоваться при определении литерала объекта, устанавливая прототип [[Prototype]]
объекта при его создании. Этот способ может рассматриваться как альтернатива методу Object.create()
. Смотрите также литеральный синтаскис инициализации объекта.
Синтаксис
var shape = {}, circle = new Circle(); // Установка прототипа объекта shape.__proto__ = circle; // Получение прототипа объекта console.log(shape.__proto__ === circle); // true
Обратите внимание: название свойства состоит из двух подчёркиваний, следующих за ними пяти символов «proto» и следующих за ними ещё двух подчёркиваний.
Описание
Геттер свойства __proto__
расширяет значение внутреннего прототипа [[Prototype]]
объекта. Для объектов, созданных с использованием литеральной формы создания объекта, это значение равно Object.prototype
. Для функций это значение равно Function.prototype
. Для объектов, созданных с использованием формы new fun
, где fun
является одной из встроенных функций-конструкторов, предоставляемых JavaScript (Array
, Boolean
, Date
, Number
, Object
, String
и так далее — включая новые конструкторы, добавленные в процессе развития JavaScript), это значение равно fun.prototype
. Для объектов, созданных с использованием формы new fun
, где fun
является функцией, определённой в скрипте, это значение равно значению fun.prototype
во время вычисления new fun
. Именно поэтому при присваивании fun.prototype
нового значения, ранее созданные экземпляры fun
продолжат использовать предыдущее значение в качестве своего прототипа [[Prototype]]
, а последующие вызовы new fun
будут использовать вновь присвоенное значение в качестве своего прототипа [[Prototype]]
.
Геттер __proto__
позволяет прототипу [[Prototype]]
объекта быть изменяемым. Объект должен быть расширяемым в соответствии с Object.isExtensible()
: если это не так, выкидывается исключение TypeError
. Предоставляемое значение должно быть объектом или null
. Предоставление любого другого значения ничего не даст.
Для понимания того, как прототипы используются для наследования, смотрите статью руководства «Наследование и цепочки прототипов».
Свойство __proto__
является простым свойством доступа на объекте Object.prototype
— свойством, состоящим из геттера и сеттера. Свойство __proto__
будет найдено, если, в конечном итоге, его поиск пройдёт через Object.prototype
, но при доступе к нему не через Object.prototype
, оно найдено не будет. Если перед просмотром Object.prototype
буден найдено какое-нибудь другое свойство __proto__
, оно скроет искомое свойство Object.prototype
.
var noProto = Object.create(null); console.log(typeof noProto.__proto__); // undefined console.log(Object.getPrototypeOf(noProto)); // null noProto.__proto__ = 17; console.log(noProto.__proto__); // 17 console.log(Object.getPrototypeOf(noProto)); // null var protoHidden = {}; Object.defineProperty(protoHidden, '__proto__', { value: 42, writable: true, configurable: true, enumerable: true }); console.log(protoHidden.__proto__); // 42 console.log(Object.getPrototypeOf(protoHidden) === Object.prototype); // true
Примеры
В следующем примере создаётся новый экземпляр Employee
, а затем проверяется, что его свойство __proto__
является тем же самым объектом, что и его конструктор prototype
.
// Декларируем функцию, используемую как конструктор function Employee() { /* инициализируем экземпляр */ } // Создаём новый экземпляр Employee var fred = new Employee(); // Проверка на эквивалентность fred.__proto__ === Employee.prototype; // true
В этот момент fred
унаследован от Employee
, однако присваивание другого объекта в fred.__proto__
может изменить это:
function Cow() { /* инициализируем экземпляр */ } // Присваиваем __proto__ новый объект fred.__proto__ = Cow.prototype;
Теперь fred
наследуется непосредственно от Cow.prototype
, а не от Employee.prototype
, и теряет свойства, изначально унаследованные от Employee.prototype
.
Однако, это применяется только к расширяемым объектам, у нерасширяемых объектов свойство __proto__
не может быть изменено:
var obj = {}; Object.preventExtensions(obj); obj.__proto__ = {}; // выкинет TypeError
Обратите внимание, что свойство __proto__
может быть переопределено даже у объекта Object.prototype
, если новая цепочка заканчивается null
:
var b = {}; Object.prototype.__proto__ = Object.create(null, // [[Prototype]] { hi: { value: function() { alert('hi'); } } }); b.hi();
Если свойство __proto__
объекта Object.prototype
не установлено в null
, или в другой объект, чья цепочка прототипов, в конечном итоге, явно не заканчивается значением null
, будет выкинуто исключение TypeError
«циклическое значение __proto__», поскольку цепочка должна заканчиваться null
(как это и происходит на Object.prototype
при нормальных обстоятельствах).
Спецификации
Спецификация | Статус | Комментарии |
---|---|---|
ECMAScript 6 (ECMA-262) Определение 'Object.prototype.__proto__' в этой спецификации. |
Кандидат в рекомендации | Включён в (нормативное) приложение для дополнительных возможностей ECMAScript для веб-браузеров (обратите внимание, что спецификация закрепляет то, что уже реализовано). |
Совместимость с браузерами
Примечание: спецификация ES6 требует поддержку свойства __proto__
только в браузерах и не требует его поддержку в других окружениях (хотя оно и рекомендуется в качестве обязательного). Если ваш код должен работать в не-браузерных окружениях, вместо свойства рекомендуется использовать методы Object.getPrototypeOf()
и Object.setPrototypeOf()
.
Возможность | Chrome | Firefox (Gecko) | Internet Explorer | Opera | Safari |
---|---|---|---|---|---|
Базовая поддержка | (Да) | (Да) | 11 | (Да) | (Да) |
Возможность | Android | Chrome для Android | Firefox Mobile (Gecko) | IE Mobile | Opera Mobile | Safari Mobile |
---|---|---|---|---|---|---|
Базовая поддержка | (Да) | (Да) | (Да) | (Да) | (Да) | (Да) |