Что такое объект JavaScript.прототип поведения?

я столкнулся со странным фрагментом кода, который я вообще не могу понять, вот он:

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

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

  1. почему это ведение журнала 5, undefined, undefined, 6 вместо undefined, 6, undefined, 6?
  2. почему замена прототипа не изменяет прототип всех экземпляров объекта, как это обычно делается?
  3. что делает двигатель V8, шаг за шагом, в этом коде?
  4. EDIT:как бы я идти об изменении прототипа всех экземпляров?

каждое объяснение приветствуется.

6 ответов


по данным ECMA Script 5 Технические характеристики,

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

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


объяснение

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

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

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

тогда, когда вы create 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 последние версии на Окна.


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

в принципе, это сводится к тому, что ссылки на объекты являются значениями, скорее похожими на числа, которые сообщают движку JavaScript (V8 в вашем случае), где находится объект в памяти. Когда вы копируете значение, вы делаете именно это: вы скопировать значение. Копирование ссылки на объект делает копию ссылки (а не объекта) и никоим образом не привязывает назначение этого значения к источнику значения, больше, чем это связывает b to 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);

давайте бросим на него ASCII-art (ну, Unicode-art), который также ответит:

  1. что делает двигатель V8, шаг за шагом, в этом коде?

после того, как вы запустите это:

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

var instance1 = new obj();

...у вас есть что-то примерно такое в памяти (игнорируя некоторые детали):

                 +−−−−−−−−−−−−−−−−−−−−−+             
                 |     (function)      |             
                 +−−−−−−−−−−−−−−−−−−−−−+             
obj<Ref55461>−−−>| prototype<Ref32156> |−−−−−−−−−−−+
                 +−−−−−−−−−−−−−−−−−−−−−+            \ 
                                                     \     
                                                      \    
                                                       \   +−−−−−−−−−−+
                                                        +−>| (object) |
                                                       /   +−−−−−−−−−−+
                                                      /    | x<5>     |
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+    /     +−−−−−−−−−−+
                      |         (object)        |   /
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+  /
instance1<Ref86545>−−>| [[Prototype]]<Ref32156> |−+
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+

поскольку ссылки на объекты являются значениями, и назначение всегда копии значения, когда V8 (или любой другой двигатель) создает instance1, это копии значение obj.prototype (показано здесь концептуально как <Ref32156>) к instance1 ' s [[Prototype]] внутренний слот.

тогда, когда вы делаете

obj.prototype = {y: 6};

...вы меняете значение в obj.prototype (показанный здесь как изменение <Ref32156> to <Ref77458>), но это не влияет на значение (<Ref32156>) в instance1 ' s [[Prototype]] слот:

                 +−−−−−−−−−−−−−−−−−−−−−+                   
                 |     (function)      |                   
                 +−−−−−−−−−−−−−−−−−−−−−+                   +−−−−−−−−−−+
obj<Ref55461>−−−>| prototype<Ref77458> |−−−−−−−−−−−−−−−−−−>| (object) |
                 +−−−−−−−−−−−−−−−−−−−−−+                   +−−−−−−−−−−+
                                                           | y<6>     |
                                                           +−−−−−−−−−−+

                                                           +−−−−−−−−−−+
                                                        +−>| (object) |
                                                       /   +−−−−−−−−−−+
                                                      /    | x<5>     |
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+    /     +−−−−−−−−−−+
                      |         (object)        |   /
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+  /
instance1<Ref86545>−−>| [[Prototype]]<Ref32156> |−+
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+

...и следовательно, когда вы делаете

var instance2 = new obj();

...у вас есть:

                 +−−−−−−−−−−−−−−−−−−−−−+                   
                 |     (function)      |                   
                 +−−−−−−−−−−−−−−−−−−−−−+                   +−−−−−−−−−−+
obj<Ref55461>−−−>| prototype<Ref77458> |−−−−−−−−−−−−−−−−+−>| (object) |
                 +−−−−−−−−−−−−−−−−−−−−−+               /   +−−−−−−−−−−+
                                                      /    | y<6>     |
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+    /     +−−−−−−−−−−+
                      |         (object)        |   /      
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+  /       
instance2<Ref98465>−−>| [[Prototype]]<Ref77458> |−+        +−−−−−−−−−−+
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+       +−>| (object) |
                                                       /   +−−−−−−−−−−+
                                                      /    | x<5>     |
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+    /     +−−−−−−−−−−+
                      |         (object)        |   /
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+  /
instance1<Ref86545>−−>| [[Prototype]]<Ref32156> |−+
                      +−−−−−−−−−−−−−−−−−−−−−−−−−+

...что объясняет console.log результат.

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

если вы хотите фактически изменить, какой объект они используют в качестве своего прототипа, вы можете сделать это только в том случае, если у вас есть ссылка на экземпляр(Ы), который вы хотите изменить, и только на движке JavaScript, поддерживающем Особенности ES2015, используя Object.setPrototypeOf или (в среде веб-браузера, и если объект в конечном итоге наследует от Object.prototype) через __proto__ accessor property (не рекомендуется):

Object.setPrototypeOf(instance1, obj.prototype);

setPrototypeOf изменяет значение [[Prototype]] внутренний слот в объекте (как и настройка __proto__, если объект имеет его).

вы не можете сделать это, если у вас нет доступа к экземплярам.

я не думаю, что вы делаете, учитывая вопрос, но если вы просто хотите изменить государство объекта, который они используют в качестве своего прототипа (возможно, добавив y), вы можете, конечно, просто установить свойства на нем, и поскольку прототипическое наследование JavaScript является "живым" (есть живая ссылка на прототип из экземпляра), вы можете получить доступ к этим свойствам в любом экземпляре, который наследует от прототипа, даже если они были созданы до того, как вы сделали изменение:

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

var instance1 = new Example();
console.log(instance1.x, instance1.y); // 5, undefined

Example.prototype.y = 6;
console.log(instance1.x, instance1.y); // 5, 6

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


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

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

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

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


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

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

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

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

obj.prototype.y=6

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