Object.prototype.__proto__

Предупреждение: Изменение прототипа [[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(). Смотрите также литеральный синтаксис инициализации объекта.

Синтаксис

js
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.

js
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.

js
// Декларируем функцию, используемую как конструктор
function Employee() {
  /* инициализируем экземпляр */
}

// Создаём новый экземпляр Employee
var fred = new Employee();

// Проверка на эквивалентность
fred.__proto__ === Employee.prototype; // true

В этот момент fred унаследован от Employee, однако присваивание другого объекта в fred.__proto__ может изменить это:

js
function Cow() {
  /* инициализируем экземпляр */
}

// Присваиваем __proto__ новый объект
fred.__proto__ = Cow.prototype;

Теперь fred наследуется непосредственно от Cow.prototype, а не от Employee.prototype, и теряет свойства, изначально унаследованные от Employee.prototype.

Однако, это применяется только к расширяемым объектам, у нерасширяемых объектов свойство __proto__ не может быть изменено:

js
var obj = {};
Object.preventExtensions(obj);

obj.__proto__ = {}; // выкинет TypeError

Обратите внимание, что свойство __proto__ может быть переопределено даже у объекта Object.prototype, если новая цепочка заканчивается null:

js
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 при нормальных обстоятельствах).

Спецификации

Specification
ECMAScript Language Specification
# sec-object.prototype.__proto__

Совместимость с браузерами

BCD tables only load in the browser

Примечание: Спецификация ES6 требует поддержку свойства __proto__ только в браузерах и не требует его поддержку в других окружениях (хотя оно и рекомендуется в качестве обязательного). Если ваш код должен работать в не-браузерных окружениях, вместо свойства рекомендуется использовать методы Object.getPrototypeOf() и Object.setPrototypeOf().

Смотрите также