Почему свойства экземпляра обновляются при обновлении прототипа, если свойство не было установлено самим экземпляром? - PullRequest
3 голосов
/ 10 ноября 2010

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

Позвольте мне привести пример:

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 равноиспользуется для инициализации внутреннего свойства [[Prototype]] вновь созданного объекта, прежде чем объект Function будет вызван как constructor для этого вновь созданного объекта.

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

Ответы [ 3 ]

4 голосов
/ 11 ноября 2010

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

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

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

delete myOtherObj.num;

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

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

Таким образом, когда свойство «prototype» конструктора «копируется» в [[Prototype]] для вновь созданного объекта, оно фактически получает ссылку на исходное свойство 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

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

3 голосов
/ 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 соответственно.

2 голосов
/ 11 ноября 2010

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


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

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

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


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

4.3.4

Конструктор

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

4.3.5

прототип

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

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

...