Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript property inheritance

I'm trying to have a generic 'List' class, which will have:

  • Property: Items - which would be an array of 'what-ever'
  • Method: Add() - which would be abstract and implemented by the specific 'List' object
  • Method: Count() - which returns the number of 'items'

And then create sub-classes which will inherit from '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

...but this seems to create a shared 'Items' list for all 'CDList' instances. I need to somehow have a new inherited instance of the 'Items' list for each 'CDList' instance.

How can I do this?

*I'm using in this example the 'Items' list as an example. I'd like to be able to have in my sub-classes a new instance for any type of inherited property - not necessarily an Array object.

like image 961
user1566994 Avatar asked Aug 19 '12 14:08

user1566994


People also ask

What are inherited properties in JavaScript?

An inherited property is a property the object inherits from the prototype object. Every object in JavaScript links to an object, the prototype, from which it inherits properties. The property accessor myObject. toString evaluates to a function.

What type of inheritance is used in JavaScript?

Mainly there are three types of inheritance in JavaScript. They are, prototypal, pseudo classical, and functional.

Is JavaScript prototype inheritance?

The Prototypal Inheritance is a feature in javascript used to add methods and properties in objects. It is a method by which an object can inherit the properties and methods of another object. Traditionally, in order to get and set the [[Prototype]] of an object, we use Object. getPrototypeOf and Object.

Can we inherit class in JavaScript?

To create a class inheritance, use the extends keyword.


2 Answers

There is only one Array because you only create one. This array is attached to the prototype of "CDList" and therefore shared between all instances.

To solve this problem: don't attach it to the prototype, but to the instance. This can only be done at construction time:

// 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);
};

Demo: http://jsfiddle.net/9xY2Y/1/


The general concept is: Stuff that each instance must have its own copy of (like the "Items" array in this case) must be created and attached to "this" (= the instance) at construction time, i.e. when doing new List() or new CDList(). Everything that can be shared across instances can be attached to the prototype. This essentially means that properties like the "Add" function are created exactly one time and are then used by all instances (what caused the original issue).

When linking prototypes, you must not directly link them (usually), e.g.:

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

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

Because the prototypes of the three functions "List", "CDList" and "DVDList" got directly linked to each other, they all point to one prototype object, and that is List.prototype. So, if you add something to CDList.prototype you actually add it to List.prototype - which also is the prototype of "DVDList".

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

What does the trick is to link the prototype to a new instance of the parent class:

CDList.prototype = new List();

This creates a new object of type "List()" with the special feature that the prototype of the function "List()" is linked to the new object, enabling you to call properties of the prototype directly on the object:

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"

However, remember that we intended to use the body of the function "List()" to create stuff like this array "Items" on a per-instance basis? It is the place where you put any "constructor" code, e.g.

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

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

One very clean solution is to use another function to create a prototype-object:

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

It is now okay to directly link the prototypes, because we will never add anything to ctor.prototype:

ctor.prototype = List.prototype;

If we then do:

CDList.prototype = new ctor();

the prototype of "CDList()" becomes a new object of type "ctor", that has no own properties but can be extended, e.g. by a new "Add" function:

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

However, if you do not add an "Add" property to this new prototype object, the prototype of "ctor()" kicks in - which is the prototype of "List()". And that's the desired behavior.

Also, the code in "List()" is now only executed whenever you do new List() or when you call it directly from another function (in a child class via List.call(this);).

like image 113
Niko Avatar answered Oct 16 '22 20:10

Niko


Try this:

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

You need to call the superconstructor...

I like this article of the MDN network about JavaScript inheritance. I tried this method/technique and it works very fine in all browsers I tested (Chrome, Safari, Internet Explorer 8+, and Firefox)..

like image 24
philipp Avatar answered Oct 16 '22 20:10

philipp