Что такое поведение 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
преуспеть в изменении всех существующих объектов. Таким образом, ответ, очевидно, заключается в том, что вы не должны назначать новый объект в качестве прототипа, а просто модифицировать текущий прототип.