javascript inheritance

I know there is a lot of similar questions are tons of great answers to this. I tried to look at the classical inheritance methods, or those closure methods etc. Somehow I consider they are more or less "hack" methods to me, as it doesn't really what the javascript is designed to do. (Welcome anybody correct me if I am wrong). OK, as long as it works, I satisfy with the classical inheritance pattern like:

PARENTClass = function (basevar) { do something here; };
PARENTClass.prototype = { a: b, c: d}; // prototype is auto gen 

// Inheritance goes here
CHILDClass = function (childvar) { do something; };
CHILDClass.prototype = new PARENTClass(*1); // Actual inheritance to the prototype statement
// Instance
CHILDInstance = new CHILDClass(whatever);

Above is somehow, to my understanding the inheritance of JS. But one scenario I have no idea how to implement, is that what if I want to do some initializing DURING object creation (ie, within constructor), and the new object can be used right away.... My illustration on problem might not be too clear, so let me use the following C# Psuedo to explain what I want to do:

class PARENT {
    public PARENT (basevar) { ... }
}
class CHILD : PARENT {
    public CHILD (basevar) : PARENT (basevar) // constructor of child, and call parent constructor during construct.
    { ... }
}

For some reason (like init. UI elements), putting them in constructor seems the best way to do. Anyone have idea on how can I do it.

PS: in the *1, I have no idea what I should put there. PS2: The above situation I DID found the jquery.inherit library can do, I just wonder if not using library can achieve it. PS3: Or my understanding is wrong. Since javascript is not intended to mimick OOP (that's why i call it hack), what is the "CORRECT" logic to implement this.

Answers:

Answer

It is not a hack as such; JavaScript is a prototyped language, as defined by Wikipedia as where:

..classes are not present, and behavior reuse (known as inheritance in class-based languages) is performed via a process of cloning existing objects that serve as prototypes.

As it says, classes are not used in JavaScript; each object that you create is descended from the JavaScript Object; all objects in JavaScript have the prototype object, and all instances of objects you create 'inherit' methods and properties from their object's prototype object. Take a look at the MDC prototype object reference for more information.

As of this, when you call the line:

CHILDClass.prototype = new PARENTClass();

This allows the CHILDClass object to add methods and properties to its prototype object from the PARENTClass object, which creates an effect similar to the idea of inheritance present in class-based languages. Since the prototype object affects every instance created of that object, this allows the parent object's methods and properties to be present in every instance of your child object.

If you want to call your parent class's constructor in your child class's constructor, you use the JavaScript call function; this allows you to call the parent class's constructor in the context of the child class's constructor, therefore setting the newly prototyped properties in your child class to what they are set as in the parent class.

You also do not need to put anything where you have specified the *1, since that line is merely used to add the methods and properties to the child class's prototype object; however, bear in mind that it calls the parent class's constructor, so if there are any arguments that are fundamental in the operation of the parent class constructor, you should check that these are present so as to avoid JavaScript errors.

Answer

You can manually invoke the parent constructor in the subclass constructor like this:

CHILDClass = function (basevar) {
  PARENTClass.call(this, basevar);
  // do something;
};

The trick here is using the call method, which allows you to invoke a method in the context of a different object. See the documentation of call for more details.

Answer

JavaScript has no built-in support for inheritance hierarchies as type extension is supposed to be done via aggregation, ie adding desired functionality directly to the object itself or its prototype if the property is to be shared between instances.

Nevertheless, JS is powerful enough to make implementing other forms of object construction possible, including classical inheritance.

Given a clone function - which is enough to add 'true' prototypical inheritance, and not JavaScript's bastardization thereof - your exampe can be implemented like this:

function ParentClass(baseVar) {
    // do stuff
}

// don't overwrite the prototype object if you want to keep `constructor`
// see http://joost.zeekat.nl/constructors-considered-mildly-confusing.html
ParentClass.prototype.a = 'b';
ParentClass.prototype.c = 'd';

function ChildClass(childVar) {
    // call the super constructor
    ParentClass.call(this, childVar);
}

// don't inherit from a ParentClass instance, but the actual prototype
ChildClass.prototype = clone(ParentClass.prototype);
ChildClass.prototype.e = 'f';

It's also possible to add some syntactic sugar for class-based inheritance - my own implementation can be found here.

The example from above would then read

var ParentClass = Class.extend({
    constructor: function(baseVar) {
        // do stuff
    },
    a: 'b',
    c: 'd'
});

var ChildClass = ParentClass.extend({
    e: 'f'
});
Answer

I've got a lightweight javascript OOP wrapper that provides 'Class-like' inheritance where you can override base methods or call base constructors or members.

You define your classes like this:

//Define the 'Cat' class
function Cat(catType, firstName, lastName)
{
    //Call the 'Animal' constructor.
    Cat.$baseNew.call(this, firstName, lastName);

    this.catType = catType;
}
//Extend Animal, and Register the 'Cat' type.
Cat.extend(Animal, { type: 'Cat' }, {
    hello: function(text)
    {
        return "meaoow: " + text;
    },
    getFullName: function()
    {
        //Call the base 'Animal' getFullName method.
        return this.catType + ": " + Cat.$base.getFullName.call(this);
    }
})

//It has a built-in type system that lets you do stuff like:

var cat = new Cat("ginger", "kitty", "kat");
Cat.getType()                     // "Cat"
cat.getBaseTypesAndSelf()         // ["Cat","Animal","Class"]
cat.getType()                     // "Cat"
cat.isTypeOf(Animal.getType())    // "True"

var dynamicCat = Class.createNew("Cat", ["tab","fat","cat"])
dynamicCat.getBaseTypesAndSelf()  // ["Cat","Animal","Class"]
dynamicCat.getFullName()          // tab: fat cat

source code available at: Class.js

I also have more details in my blog post about OOP in javascript

Answer

Just thought I'd mention some of the issues with the classical pattern you're going for:

  1. Reference vars on the super class(es) will be available as essentially statics on ALL instances. For example, if you have var arr = [1,2,3] in the super, and do instance_1.arr.push(4) instance_2.arr.push(5) ALL of these instances will "see" the changes.
  2. So you solve 1. with Ayman's solution which Zakas calls "Constructor Stealing", but now you call the constructor twice: once for your prototype and once for the constructor stealing. Solution - for your prototype use a helper like inheritPrototype (I showed the whole implementation of this in this post: inheritPrototype method FWIW, this essentially came from a combination of page 181 of Zakas's book and some Crockford study.

  3. No privacy (but then again, you'd need to use something like the Durable Object pattern to get this and that may not be what you want)

  4. Object definition is left "dangling": Solution - put an if statement checking for any of your prototype's functions and then define the prototype with a prototype literal.

I have running examples of all of this on github!!!

It's been just as much of a challenge for me to truly grok both: Zakas and Crockford books on object creation and inheritance. I also needed to try some different JavaScript TDD frameworks. So I decided to create an essay on both TDD Frameworks and JavaScript Object Creation & Inheritance. It has running code and jspec tests! Here's the link:* My GitHub Open Source Essay/Book

Answer

the_drow:

I left a comment above about calling the super constructor twice with your solution - but feel a bit bad about leaving you hanging on the implementation of inheritPrototype. First of all, credit to Nicholas Zakas as this is me paraphrasing his book Professional JavaScript for Web Developers, 2nd Ed (page 181):

function inheritPrototype(sub,sup) {
  var proto = object(sup.prototype);// you'll need an object create method ;)
  proto.constructor = sub;
  sub.prototype = proto;
}

Now replace your:

StreetGridView.prototype = new GridView();

with,

StreetGridView.prototype = inheritPrototype(StreetGridView,GridView);

and you've only called your GridView constructor once! But you'll notice the object method. You'll need something like:

function object(o) {
  function F(){};
  F.prototype = o;
  return new F();
}

If you've read any Douglas Crockford you've seen this one!

Shameless Plug: This stuff is hard to grok and is the exact reason I'm doing an essay on TDD JavaScript and the whole second part has a bunch of unit tests of inheritance patterns. I'm specifically examining the Zakas and Crockford books on object creation and inheritance. You don't have to read my essay (it's currently in Open Office .odt format), but you could do a lot worse than to just download my code and read it in 2 minutes! Here's the link: My Free Book

Answer

Inspiration strikes!

I couldn't get that pattern to work, so please let me know if you can spot what is wrong with it. But moving things around and using Combination Inheritance, I seem to have solved the problem.

I've included the following code and left this post on the forum to help others in future.

function GridView() {
        var _ownerElement;
    }

    GridView.prototype.getOwnerElement = function() {

        return this._ownerElement;
    }

    GridView.prototype.setOwnerElement = function(ownerElement) {
        this._ownerElement = ownerElement; 
    }

    GridView.prototype.initialize = function() {
        this.setOwnerElement('test');
    }

    function StreetGridView() {

        GridView.call(this);
    }

    StreetGridView.prototype = new GridView();

    StreetGridView.prototype.initialize = function(dataURL, ownerElement) {

        this.setOwnerElement(ownerElement);


        /* Constructor Code */

        $(this.getOwnerElement()).flexigrid
            (
                {
                    url: dataURL,
                    dataType: 'json',
                    colModel: [
                    { display: '', name: 'view', width: 20, sortable: true, align: 'center' },
                    { display: 'USRN', name: 'USRN', width: 80, sortable: true, align: 'center' },
                    { display: 'Street', name: 'Street', width: 260, sortable: true, align: 'left' },
                    { display: 'Locality', name: 'Locality', width: 200, sortable: true, align: 'left' },
                    { display: 'Town', name: 'Town', width: 200, sortable: true, align: 'left' },
                    { display: 'Open', name: 'Actions', width: 30, sortable: false, align: 'center' }
                    ],
                    sortname: "USRN",
                    sortorder: "asc",
                    usepager: true,
                    title: 'Streets',
                    useRp: true,
                    rp: 5,
                    showToggleBtn: false,
                    width: 'auto',
                    height: 'auto'
                }
            );
    }
Answer

The approach is completely different, the Resig technique creates constructor functions, this approach is also known as classical inheritance i.e.:

var Person = Class.extend({
  init: function(isDancing){
    this.dancing = isDancing;
  }
});

var p = new Person(true);

As you see, the Person object is actually a constructor function, which is used with the new operator.

With the Object.create technique, the inheritance is based on instances, where objects inherit from other objects directly, which is also known as Prototypal Inheritance or Differential Inheritance.

Answer

They are completely different.

Douglas Crockford's method creates an instance that inherits a different instance.

John Resig's approach creates a class that inherits a different class.

When using Douglas Crockford's method, you're creating a new object instance that inherits a single existing instance.

When using John Resig's approach, you're creating a constructor function, which you can then use to create instances of the inherited class.

Answer

It looks like the line #3 in the code is with the parameters inverted, but let's try to make things clearer following your questions:

For one thing, why is the child's prototype property being set on line 2 the parent object, only to be set to something else on the following line

It's just holding the child object so it can be instantiated to define the child prototype chain

And I don't understand the arguments for the call() method. I thought the 1st arg was supposed to be the function context for Array.prototype.slice(). So why is 1 passed in? And the second arg seems to be the arguments object for Function.prototype.extend().

If you check this url, you'll see that it takes two arguments, the second being optional: array.slice(begin[, end]); That said, arguments should be the first argument to .call(), as it is the context, as you've already pointed out.

But I thought you needed to use apply() if the arguments to Array.prototype.slice() are in the form of an array.

You're also right about the arguments form, it should be an array. And guess what? It is... :)
Check this http://jsfiddle.net/x52Aa/

Hope I could help you at least with the most.
Cheers

Answer
  • 'Clone' an object by making the object the prototype of a throwaway function and calling that function with 'new'.

  • Clone the parent constructor's prototype, and set the result as the prototype of the child class.

...

/**
 * Extend a constructor with a subtype
 * @param {Function} superCtor      Constructor of supertype
 * @param {Function} subCtor        Constructor of subtype
 * @return {Function}               Constructor of subtype
 */
var extend = (function(){

  return function (superCtor, subCtor) {
    var oldProto=subCtor.prototype;
    subCtor.prototype=clone(superCtor.prototype);
    return merge(subCtor.prototype, oldProto).constructor=subCtor; 
  }

  function Clone(){}

  /**
   * Clone an object
   * @param {Object}  obj     Object to clone
   * @return {Object}         Cloned object
   */
  function clone (obj) { Clone.prototype=obj; return new Clone() }

  /**
   * Merge two objects
   * @param {Object} dst      Destination object
   * @param {Object} src      Source object
   * @return {Object}         Destination object
   */
  function merge (dst, src) {
    for (var p in src) if (src.hasOwnProperty(p)) dst[p]=src[p];
    return dst;
  }

}());
Answer

Using this inheritance method, you can only mingle variables "upstream." Your child object will be able to see the public properties of its prototype, but the prototype cannot see the properties of its children. It must be self-contained.

(EDIT: I just noticed you're also using "self" without declaring it in sobj.)

sobj = function()
{
    var self = this;
    self.close = function()
    {
        clearInterval(self.interval);
    }

    self.interval = null;
}
cobj = function()
{
    var self = this;
    self.interval = setInterval(function(){ /* Do something */}, 1000);
}
// set cobj.prototype - see explanation below

For how to properly set the prototype (and an in-depth look at how inheritance works in JS), I refer you to Douglas Crockford's book JavaScript: The Good Parts.

He has actually posted how to properly set the prototype on his site. Be sure to use the version that does not touch Object.prototype, as many scripts (jQuery for starters) will break if you change it.

Answer

First thing, I don't think that var this.interval is okay. var keyword is used to define a variable, but in your case you are referencing an object.

Second, if you want to declare "interval" as a method of your cobj, you have to wrap the body of the function in a function.

So, this way it works for me:

var sobj = function()
{
    this.close = function()
    {
        clearInterval(this.interval);
    }
}
 var cobj = function()
{   
   this.initInterval = function(){ this.intervalid = setInterval(function(){ alert("test")}, 5000)};
   this.intervalid = null;
}
cobj.prototype = new sobj();

var inst = new cobj();
inst.initInterval();

After I've defined the constructor functions, I create an actual instance of a "cobj" object and then call "initInterval" to initialize the "setInterval".

UPD: updated the code per @MooGoo's comment.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.