Наследование прототипа javascript

Это кажется непоследовательным, но, вероятно, связано с тем, что я новичок в функции наследования прототипа javascript.

В принципе, у меня есть два свойства базового класса: "список" и "имя". Я создаю два подкласса и присваиваю значения свойствам. Когда я создаю экземпляр второго подкласса, он получает значения из первого экземпляра подкласса, но только для "списка", а не для "имени". Что происходит?? Конечно, я бы предпочел, чтобы любые последующие экземпляры подкласса не получали значения из других экземпляров, но если это произойдет, оно должно быть последовательным!

Вот фрагмент кода:

    function A()
    {
        this.list = [];
        this.name = "A";
    }
    function B()
    {
    }
    B.prototype = new A();

    var obj1 = new B();
    obj1.list.push("From obj1");
    obj1.name = "obj1";

    var obj2 = new B();

    //output:
    //Obj2 list has 1 items and name is A
    //so, the name property of A did not get replicated, but the list did
    alert("Obj2 list has " + obj2.list.length + " items and name is "+ obj2.name);    

Ответы

Ответ 1

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

В вашем случае вы фактически не переназначили прототипированное свойство, вы изменили значение базовой структуры, которая была найдена в этом свойстве.

Это означает, что все объекты, которые совместно используют прототип A, фактически используют реализацию A. Это означает, что любое состояние, содержащееся в A, будет найдено во всех экземплярах B. В тот момент, когда вы выполняете присвоение этому свойству на экземпляр B, вы фактически заменили то, на что указывает этот экземпляр (и только тот экземпляр) (я считаю, что это связано с тем, что в B есть новое свойство "name" на B, которое попадает в цепочку областей видимости, прежде чем оно достигнет "name" на A).

EDIT:

Чтобы дать более явный пример того, что происходит:

B.prototype = new A();
var obj1 = new B();

Теперь, когда мы читаем первый раз, intrereter делает что-то вроде этого:

obj1.name;

Интерпретатор: "Мне нужно имя свойства. Сначала проверьте, что B. B не имеет" name ", поэтому давайте продолжим цепочку областей действия. Затем, A. Aha! A имеет" имя ", верните, что"

Однако, когда вы пишете, интерпретатор действительно не заботится о наследуемом свойстве.

obj1.name = "Fred";

Интерпретатор: "Мне нужно назначить имя свойства". Я попадаю в сферу действия этого экземпляра B, поэтому назначаю "Fred" B. Но я оставляю все остальное дальше по цепочке областей (т.е. А) "

Теперь, в следующий раз, когда вы прочитаете...

obj1.name;

Интерпретатор: "Мне нужно имя свойства". Ага! В этом экземпляре B есть свойство "name" , которое уже сидит на нем. Просто верните это - меня не волнует остальная цепочка областей видимости (то есть A. имя) "

Итак, в первый раз, когда вы пишете имя, он вставляет его как свойство первого класса в экземпляр и больше не заботится о том, что находится на имени AA, все еще существует, он просто дальше по цепочке областей и интерпретатор JS не доходит до того, как он нашел то, что искал.

Если "имя" имеет значение по умолчанию в (как у вас есть, то есть "A" ), вы увидите это поведение:

B.prototype = new A();
var obj1 = new B();
var obj2 = new B();

obj1.name = "Fred";

alert(obj1.name); // Alerts Fred
alert(obj2.name); // Alerts "A", your original value of A.name

Теперь, в случае вашего массива, вы никогда не заменили список в цепочке областей с новым массивом. То, что вы сделали, это захватить дескриптор самого массива и добавить к нему элемент. Следовательно, затрагиваются все экземпляры B.

Интерпретатор: "Мне нужно получить свойство" списка "и добавить к нему элемент. Проверка этого экземпляра B... nope, не имеет свойства" list ". Далее в цепочке областей: A. Да, A имеет" список ", используйте это. Теперь нажмите на этот список"

Это не так, если вы это сделали:

obj1.list = [];
obj1.push(123);

Ответ 2

  • Вы говорите в этом коде B: реализация A.
  • Тогда вы говорите, что obj1 - это новый экземпляр B и так должен захватить все значений A.
  • Затем вы добавляете элемент в ссылочное расположение списка в obj2 который, в свою очередь, добавляет элемент к ссылочное расположение списка в A (потому что они ссылаются на одно и то же).
  • Затем вы меняете значение имени в obj1.
  • Тогда вы говорите, что obj2 - это НОВЫЙ экземпляр B и так должен захватить все значений A.

Вы изменили значения списка, указанного в A, но не сами ссылки. obj2.name должно быть "A".

Ответ 3

Конечно, я бы предпочел, чтобы любой последующие экземпляры подкласса не получают значения из других экземпляров, но если это произойдет, должно быть последовательны!

Тогда не наследуйте от нового экземпляра A, наследуйте от прототипа A. Это гарантирует, что вы не наследуете состояние, вы только наследуете поведение. Это сложная часть с прототипным наследованием, она наследует и государство, а не только поведение, как в классическом наследовании ООП. В JavaScript функции конструктора должны использоваться только для настройки состояния экземпляра.

var A = function (list) {
    this.list = list;
    this.name = "A";
};

A.prototype.getName = function () {
    return this.name;
};

var B = function (list) {
    this.list = list;
    this.name = "B";
};

B.prototype = A.prototype;   // Inherit from A prototype
B.prototype.constructor = B; // Restore constructor object

var b = new B([1, 2, 3]);

// getName() is inherited from A prototype
print(b.getName()); // B

И, кстати, если вы хотите что-то изменить в A, B, вы должны это сделать:

B.prototype.name = "obj1";

Ответ 4

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

obj1.list = ["from obj1"]
...
console.log(obj2.list) // <--- Will log [] to console

Ответ 5

Вы повторно назначаете переменную имени. Дело в том, что вы переназначаете новую переменную имени в obj1. Ваше объявление имени переопределяет декларацию на объекте прототипа (поэтому вы его не видите). Со списком вы изменяете список, не изменяя его ссылку.

Ответ 6

Это происходит потому, что вы не назначаете значение "list", вы его изменяете.

Это совершенно разумное поведение, когда вы понимаете, как работает цепочка прототипов. Когда вы ссылаетесь на obj1.list, сначала выясняется, существует ли в obj1 "список", использует это, если он найден, и в противном случае использует тот, который найден на obj1.prototype(который является MyClass.prototype.list).

Итак:

obj1.list.push("test"); // modifies MyClass.prototype.list
obj1.list = ["new"]; // creates a "list" property on obj1
obj1.list.push("test"); // modifies obj1.list, not MyClass.prototype.list
delete obj1.list; // removes the "list" property from obj1
// after the delete, obj1.list will point to the prototype again
obj1.list.push("test"); // again modifies MyClass.prototype.list

Самый важный взлет - это: "прототипы - это не классы". Прототипы могут делать достаточно хорошую работу по подделке классов, но они не являются классами, поэтому вы не должны относиться к ним как таковым.