Наследование свойств Javascript

Я пытаюсь создать общий класс List, который будет иметь:

  • Свойство: Элементы - это будет массив "что-никогда"
  • Метод: Добавить() - который будет абстрактным и реализован конкретным объектом "Список"
  • Метод: Count() - который возвращает количество элементов

а затем создайте подклассы, которые наследуются от "List"

//Class 'List'
function List(){
    this.Items = new Array();
    this.Add = function(){ alert('please implement in object') }
}

//Class CDList - which inherits from 'List'
function CDList(){
    this.Add = function(Artist){
        this.Items.push(Artist)
    }
}
CDList.prototype = new List();
CDList.prototype.constructor = CDList;

//Create a new CDList object
var myDiscs = new CDList();
myDiscs.Add('Jackson');
myDiscs.Count()  <-- this should be 1


//Create a second CDList object
var myDiscs2 = new CDList();
myDiscs2.Add('Walt');
myDiscs2.Add('Disney');
myDiscs2.Count()  <-- this should be 2

.. но это, похоже, создает общий список "Items" для всех экземпляров "CDList". Мне нужно как-то добавить новый унаследованный экземпляр списка "Items" для каждого экземпляра "CDList".

Как я могу это сделать?

* Я использую в этом примере список "Items" в качестве примера. Я бы хотел иметь в своих подклассах новый экземпляр для любого типа унаследованного свойства - не обязательно объект Array.

Спасибо, ребята!

Ответ 1

Существует только один массив, потому что вы только создаете его. Этот массив привязан к прототипу "CDList" и поэтому разделяется между всеми экземплярами.

Чтобы решить эту проблему: не прикрепляйте ее к прототипу, а к экземпляру. Это можно сделать только во время строительства:

// This is the constructor of the parent class!
function List() {
    this.Items = new Array();
}

// Add methods to the prototype, not to the instance ("this")
List.prototype.Add = function() { alert('please implement in object'); };

// Constructor of the child
function CDList() {
    List.call(this); // <-- "super();" equivalent = call the parent constructor
}

// "extends" equivalent = Set up the prototype chain
// Create a new, temporary function that has no other purpose than to create a
// new object which can be used as the prototype for "CDList". You don't want to
// call "new List();", because List is the constructor and should be called on
// construction time only. Linking the prototypes directly does not work either,
// since this would mean that overwriting a method in a child overwrites the
// method in the parents prototype = in all child classes.
var ctor = function() {};
ctor.prototype = List.prototype;
CDList.prototype = new ctor();
CDList.prototype.constructor = CDList;

// Overwrite actions
CDList.prototype.Add = function(Artist) {
    this.Items.push(Artist);
};

Демо: http://jsfiddle.net/9xY2Y/1/


Общая концепция такова: Вещь, что каждый экземпляр должен иметь свою собственную копию (например, массив "Items" в этом случае), должен быть создан и привязан к "this" (= экземпляру) во время построения, т.е. при выполнении new List() или new CDList(). Все, что можно разделить между экземплярами, можно подключить к прототипу. Это по существу означает, что свойства, подобные функции "Добавить" , создаются ровно один раз и затем используются всеми экземплярами (что вызвало исходную проблему).

При связывании прототипов вы не должны напрямую связывать их (обычно), например:

CDList.prototype = List.prototype;
DVDList.prototype = List.prototype;

// Now add a new function to "CDList"
CDList.prototype.Foo = function() { alert('Hi'); };

Поскольку прототипы трех функций "Список", "CDList" и "DVDList" напрямую связаны друг с другом, все они указывают на один объект-прототип, а это List.prototype. Итак, если вы добавите что-то в CDList.prototype, вы фактически добавите его в List.prototype, который также является прототипом "DVDList".

var dvd = new DVDList();
dvd.Foo(); // <-- alerts "hi" (oops, that wasn't intended...)

Что трюк заключается в том, чтобы связать прототип с новым экземпляром родительского класса:

CDList.prototype = new List();

Это создает новый объект типа "Список()" со специальной функцией, с которой прототип функции "Список()" связан с новым объектом, позволяя вам вызывать свойства прототипа непосредственно на объекте:

var l = new List();
alert( l.hasOwnProperty("Add") );  // <-- yields "false" - the object l has no
                                   // property "Add"
l.Add("foo"); // <-- works, because the prototype of "List" has a property "Add"

Однако помните, что мы намеревались использовать тело функции "Список()" для создания таких вещей, как этот массив "Элементы" для каждого экземпляра? Это место, где вы помещаете какой-либо код "конструктора", например.

function User(userId) {
    $.getJSON('/user/' + userId, ...
}

function Admin() {}
Admin.prototype = new User( // ... now what?

Одно очень чистое решение - использовать другую функцию для создания прототипа-объекта:

var ctor = function() {}; // <-- does nothing, so its super safe
                          // to do "new ctor();"

Теперь можно напрямую связать прототипы, потому что мы никогда ничего не добавим к ctor.prototype:

ctor.prototype = List.prototype;

Если мы тогда сделаем:

CDList.prototype = new ctor();

прототип "CDList()" становится новым объектом типа "ctor", который не имеет собственных свойств, но может быть расширен, например. новой функцией "Добавить" :

CDList.prototype.Add = function() { /* CD specific code! */ };

Однако, если вы не добавляете свойство "Добавить" к этому новому объекту прототипа, прототип "ctor()" запускается - это прототип "List()". И это желаемое поведение.

Кроме того, код в "List()" теперь выполняется только при выполнении new List() или когда вы вызываете его непосредственно из другой функции (в дочернем классе через List.call(this);).

Ответ 2

попробуйте следующее:

function CDList(){
    List.call( this )
    this.Add = function(Artist){
        this.Items.push(Artist)
    }
}

вам нужно вызвать суперконструктор...

Мне нравится эта статья сети mdn о наследовании javascript. Я пробовал этот метод/метод, и он отлично работает во всех проверенных мной браузерах (Chrome, Safari, IE8 +, FF).