Что такое поведение JavaScript Object.prototype?

Я столкнулся с странным фрагментом кода, который я совсем не понимаю, вот он:

var obj = function() {};
obj.prototype.x = 5;

var instance1 = new obj();

obj.prototype = {y: 6};

var instance2 = new obj();

console.log(instance1.x, instance1.y, instance2.x, instance2.y);
// 5, undefined, undefined, 6

Теперь вопросы:

  • Почему этот журнал 5, undefined, undefined, 6 вместо undefined, 6, undefined, 6?
  • Почему замена прототипа не меняет прототип всех экземпляров объекта, как это обычно бывает?
  • Что делает двигатель V8, шаг за шагом, в этом коде?
  • EDIT: Как я могу изменить прототип всех экземпляров?

Каждое объяснение оценено.

Ответ 1

Согласно спецификации ECMA Script 5,

Значение свойства prototype используется для инициализации внутреннего свойства [[Prototype]] для вновь созданного объекта до того, как объект Function вызывается как конструктор для этого вновь созданного объекта.

Понятно, что prototype - это просто инициализировать свойство [[Prototype]]. Когда мы создаем объект, [[Prototype]] устанавливается как объект-конструктор prototype и устанавливается цепочка прототипов. В вашем случае, когда вы делаете

var obj = function() {};
obj.prototype.x = 5;

var instance1 = new obj();

[[Prototype]] выглядит следующим образом

console.log(Object.getPrototypeOf(instance1));
# { x: 5 }

(Да, вы можете получить доступ к [[Prototype]] с помощью функции Object.getPrototypeOf)

Итак, когда JS Engine ищет x в instance1, он находит значение как 5, а поскольку y не определен, он использует undefined.

Во втором случае

obj.prototype = {y: 6};

var instance2 = new obj();

вы меняете объект prototype obj, так что новые объекты, созданные с помощью этих функций, будут использовать новый объект, назначенный ему. Итак, [[Prototype]] выглядит так: instance2

console.log(Object.getPrototypeOf(instance2));
# { y: 6 }

Вот почему instance2 не смог найти в нем x, но y.


Чтобы ответить на обновленный вопрос,

EDIT: Как я могу изменить прототип всех экземпляров?

Вы можете изменить прототип старого объекта с помощью Object.setPrototypeOf, как этот

Object.setPrototypeOf(instance1, {
    y: 6
});

Так как это делает [[Prototype]] of instance1 отличным от instance2, мы можем просто обновить объект конструктора prototype, как этот

delete obj.prototype.x;
obj.prototype.y = 6;

Теперь мы не изменили внутреннее свойство как instance1, так и instance2. Мы можем проверить, что вот так

console.log(Object.getPrototypeOf(instance1) === Object.getPrototypeOf(instance2));
# true
console.log(Object.getPrototypeOf(instance1) === obj.prototype);
# true

Примечание.. Соглашение состоит в том, чтобы называть функции конструктора с начальной буквой заглавной буквой.

Ответ 2

Объяснение

Итак, во-первых, ваши две строки кода создают функцию obj и назначают ей прототип {x: 5}.

Когда вы создаете экземпляр этого объекта, он, кажется, имеет внутреннюю ссылку на прототип, который существовал, когда он был new 'd.

После этого вы переназначите прототип на {y: 6}, который не влияет на внутреннюю ссылку instance1 на первый прототип.

Затем, когда вы создаете instance2, у него есть внутренняя ссылка на 2-й прототип, и поэтому запись в них будет производить 5, undefined, undefined, 6.

# 4

Вы могли бы вместо переназначения прототипа новому объекту:

obj.prototype = {y: 6};

Измените вместо этого прототип:

delete obj.prototype.x; // Setting to undefined should produce same behaviour
obj.prototype.y = 6;

Это приведет к выходу: undefined, 6, undefined, 6

Я тестировал это с http://jsfiddle.net/9j3260gp/ в последних версиях Chrome и Firefox в Windows.

Ответ 3

  • Почему этот журнал 5, undefined, undefined, 6 вместо undefined, 6, undefined, 6?
  • Почему замена прототипа не меняет прототип всех экземпляров объекта, как это обычно бывает?

В сущности, это сводится к тому, что ссылки на объекты - это значения, а скорее числа, которые указывают движок JavaScript (V8 в вашем случае), где объект находится в памяти. Когда вы копируете значение, вы делаете именно это: вы копируете значение. Копирование ссылки на объект делает копию ссылки (а не объекта) и никоим образом не связывает место назначения этого значения с источником значения больше, чем это связывает b с a:

var a = 5;
var b = a;
a = 6;
console.log(b, a); // 5, 6

Итак, ваш код регистрирует то, что он регистрирует, и не меняет прототип instance1, по той же причине этот код записывает то же самое и не меняет значение instance1.p:

var foo = {x: 5};
var instance1 = {p: foo};

foo = {y: 6};

var instance2 = {p: foo};

console.log(instance1.p.x, instance1.p.y, instance2.p.x, instance2.p.y);

Ответ 4

прототип - это функция, которая сочетается с новыми за кулисами. Он применяется ко всем экземплярам этой функции, используемой с новым. В первом примере вы добавляете .x = 5 к прототипу, а созданный экземпляр имеет значение .x = 5 в качестве значения. Позже вы модифицируете прототип для нового объекта. Теперь это прототип, который используется в любых новых экземплярах. Вот почему первый экземпляр имеет .x = 5, а второй имеет только .y = 6

Ответ 5

Прототип экземпляров не ссылается на класс, вместо этого они ссылаются на сам объект-прототип. Это станет ясным, когда вы попробуете Object.getPrototypeOf(), чтобы увидеть, какой прототип объекта ссылается на экземпляр.

Object.getPrototypeOf(instance1)
Object { x: 5, 1 more… }

Object.getPrototypeOf(instance2)
Object { y: 6 }

Это поле getPrototypeOf должно быть внутренним, которое существует для каждого экземпляра. До getPrototypeOf существовало, вы могли бы получить это через __proto__.

Ответ 6

Потому что instance1 уже создан. Ключевое слово new создает новый объект, выполняя функцию-конструктор, которая в вашем случае obj.

Поскольку вы изменили прототип после инициализации первого экземпляра, вы больше не имеете того же состояния (например, прототип) конструктора и не можете создать тот же объект. Но созданные все еще существуют, ссылаясь на старый прототип.

Когда вы снова используете конструктор obj, вы создаете другой объект, который может быть очень грубо переведен в классический тип терминологии наследования как экземпляр другого класса.

Изменить: # 4 Эта скрипка: http://jsfiddle.net/doy3g1fh/ показывает, что

obj.prototype.y=6

преуспеть в изменении всех существующих объектов. Таким образом, ответ, очевидно, заключается в том, что вы не должны назначать новый объект в качестве прототипа, а просто модифицировать текущий прототип.