Почему свойства экземпляра обновляются при обновлении прототипа, если свойство не было установлено самим экземпляром?

Почему свойства экземпляра, в котором хранятся примитивные значения, а не ссылки, обновляются при обновлении прототипа, если свойство не было установлено самим экземпляром?

Приведу пример:

var Obj = function () {};
Obj.prototype.num = 1;

var myObj = new Obj();
var myOtherObj = new Obj();
console.log(myObj.num); //logs 1
console.log(myOtherObj.num); //logs 1

//After instances are created they still share the value (which is strange):
Obj.prototype.num = 3;
console.log(myObj.num); //logs 3
console.log(myOtherObj.num); //logs 3

//Update one of the instances property
myObj.num += 2;
console.log(myObj.num); //logs 5
console.log(myOtherObj.num); //logs 3

//Here it gets weird:
Obj.prototype.num = 4;
console.log(myObj.num); //logs 5 not updated
console.log(myOtherObj.num); //logs 4 updated

Здесь есть пара странностей:

После создания экземпляра обновление определения класса обновляет значение экземпляра тогда и только тогда, когда оно никогда не обновлялось самим экземпляром.

Если я попытаюсь объяснить это, мне покажется, что изначально у экземпляра нет собственного свойства num, и поиск происходит там, где оно обнаружено в прототипе, но как только вы действительно установите свойство (внутренне с помощью this.num или внешне с помощью instanceName.num), вы создать свойство экземпляра.

Это не похоже на то, что говорится в спецификации ECMA5:

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

Похоже, он должен заполнить экземпляры внутреннего [[Prototype]], но все же его можно изменить, обновив свойство прототипа конструктора. Я понимаю, что если я установлю свойство определения класса для объекта, такого как массив, ссылка фактически передается экземпляру, и обновление массива в любом месте изменит свойство для всех, но это примитивные значения, и, похоже, имеет много смысла.


person Bjorn    schedule 10.11.2010    source источник


Ответы (3)


Это работает так: сначала свойства ищутся в объекте, а затем, если они не найдены, просматриваются в прототипе.

Если я попытаюсь объяснить это, мне покажется, что изначально у экземпляра нет собственного свойства num, и поиск происходит там, где оно обнаружено в прототипе, но как только вы фактически устанавливаете свойство (внутренне с помощью this.num или внешне с помощью instanceName .num) вы создаете свойство для instance.property экземпляра.

Именно так это и работает. Если вы хотите удалить свойство, установленное для объекта, вы можете использовать delete:

delete myOtherObj.num;

это удалит свойство myOtherObj num и позволит показать версию прототипа.

Часть спецификации, которую вы процитировали, пытается объяснить, как внутреннее свойство [[Prototype]] устанавливается для вновь созданных экземпляров. Помимо своей роли в поиске свойств, [[Prototype]] похож на любую другую ссылку на объект Javascript.

Итак, когда свойство «прототип» конструктора «копируется» в [[Prototype]] для вновь созданного объекта, оно фактически получает ссылку на исходное свойство прототипа в конструкторе, которое можно изменить позже.


Другой пример:

function Obj() {};
Obj.prototype.num=1;

var myObj = new Obj();
console.log(myObj.num); // logs 1

Obj.prototype = {num:3}; // replaces Obj.prototype
console.log(myObj.num); // logs 1

var myOtherObj = new Obj(); 
console.log(myOtherObj.num); // logs 3
console.log(myObj.num); // still logs 1

Обратите внимание, что после создания объекта он сохраняет ссылку на любой объект, который был установлен в качестве прототипа конструктора во время вызова конструктора. Изменение свойства этого объекта по-прежнему повлияет на любые другие ссылки на тот же прототип.

person Mark Bessey    schedule 10.11.2010
comment
Ага. В этом есть смысл. Спасибо. - person Bjorn; 11.11.2010

Это совершенно нормально, а не странно. ;)

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

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

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

Чтобы уточнить:

Obj.prototype.num = 3;
console.log(myObj.num); //logs 3
console.log(myOtherObj.num); //logs 3

Здесь вы еще читаете Obj.prototype.num.

myObj.num += 2;

Здесь вы фактически читаете Obj.prototype.num и записываете в myObj.num.

Obj.prototype.num = 4;
console.log(myObj.num); //logs 5 not updated
console.log(myOtherObj.num); //logs 4 updated

Теперь вы читаете myObj.num и Obj.prototype.num соответственно.

person Marcel Korpel    schedule 10.11.2010

Другие объяснят это лучше, чем я, поэтому другие пользователи SO, не стесняйтесь критиковать мой ответ. Я удалю, если он будет ужасно вопиющим.


Думайте об этом как о наследовании. Если объект, который был создан из конструктора, не имеет своего собственного свойства num, он ищет значение в своем прототипе (или родительском, или супер, или что-то еще).

Итак, какой бы ни была эта ценность, вы ее получите. Обновите его, и любой объект, который на него смотрит, найдет это обновленное значение.

Я считаю, что прототипированные свойства не копируются для каждого экземпляра, а скорее «просматриваются» экземпляром. Таким образом, значения прототипированных свойств на самом деле не увеличивают размер объекта, поскольку все они используют один и тот же набор.


РЕДАКТИРОВАТЬ: Это тоже может помочь. Разделы 4.3.4 и 4.3.5 в разделе «Определения» в спецификации, 5-е издание.

4.3.4

конструктор

Функциональный объект, который создает и инициализирует объекты. ПРИМЕЧАНИЕ. Значение свойства «прототип» конструктора - это объект-прототип, который используется для реализации наследования и общих свойств.

4.3.5

прототип

объект, который предоставляет общие свойства для других объектов.

ПРИМЕЧАНИЕ. Когда конструктор создает объект, этот объект неявно ссылается на свойство «прототип» конструктора с целью разрешения ссылок на свойства. На свойство «прототип» конструктора можно ссылаться с помощью программного выражения constructor.prototype, а свойства, добавленные к прототипу объекта, совместно используются посредством наследования всеми объектами, имеющими общий прототип. В качестве альтернативы новый объект может быть создан с явно указанным прототипом с помощью встроенной функции Object.create.

person user113716    schedule 10.11.2010