3
\$\begingroup\$

I'm quite new to JavaScript, and I'm trying to understand the idea behind inheritance in JavaScript. I've read a bit about it, and I don't want to mess with prototypes (which I don't yet fully understand) so, I wrote this code using the xtend module for node.js.

var xtend = require("xtend");

// Human constructor.
// Human is a base class, it can introduce itself, and knows it's age.
function createHuman(name, age) {

    function getName() {
        return name;
    }

    function getAge() {
        return age;
    }

    return {
        getName : getName,
        getAge : getAge,
    };

}

// Boy constructor
// Boy *is a* Human
// Boy can play football (but not yet)
function createBoy(name, age) {

    function playFootball() {
        console.warn("Football skills not implemented yet, but trying", "-", this.getName());
        // TODO: implement this
    }

    return xtend(createHuman(name, age), {
        playFootball: playFootball,
    })

}

// Girl constructor
// Girl *is a* Human
// Girl can sing (well, almost)
function createGirl(name, age) {

    function singASong() {
        console.warn("Singing not implemented yet, but trying", "-", this.getName());
        // TODO: implement this
    }

    return xtend(createHuman(name, age), {
        singASong: singASong,
    })

}

// Hermaphrodite constructor
// Hermaphrodite *is a* Girl **and** Hermaphrodite *is a* Boy, so we have multiple inheritance?
// Hermaphrodite can do what boys and girls can
function createHermaphrodite(name, age) {
    return xtend(createGirl(name, age), createBoy(name, age));
}


// Tests:
var boy = createBoy("John", 12);
var boy2 = createBoy("Frank", 10);
var girl = createGirl("Daisy", 7);

var herm = createHermaphrodite("Mel", 24);

console.info("Boy:  ", boy.getName(), boy.getAge());
console.info("Boy:  ", boy2.getName(), boy2.getAge());
console.info("Girl: ", girl.getName(), girl.getAge());
console.info("Hermaphrodite: ", herm.getName(), herm.getAge());

boy.playFootball();
boy2.playFootball();
girl.singASong();

herm.playFootball();
herm.singASong();

As you can see, I'm not using the usual Class approach, but instead have a kind of constructors that create objects. The inheritance is made using the xtend module, which copies properties from another objects. This - I think - also allows for multiple inheritance (see Hermaphrodite).

Questions:

  1. Can this code be considered good practice? I always thought of JS as a more functional (than OO) language, so not trying to imitate classes, not using the new keyword and so on sounded good to me.

  2. Is this fast / memory-efficient? I mean - there is an additional object created every time the sub-class instance is created, just to copy the properties (for example, if I create a Boy instance, there is a Human created just to copy the properties to Boy).

  3. Is there a better / faster / more efficient (or simply: proper) way of doing this? (Pointing to one, remember that I'm a beginner, so be descriptive or I might not understand the answer.)

\$\endgroup\$
  • 2
    \$\begingroup\$ To learn how to create factory objects, try reading Addy Osmoni's "JavaScript Design Patterns" \$\endgroup\$ – Prisoner ZERO Jun 27 '12 at 13:07
  • \$\begingroup\$ Wow, this is really something. I've also found the Eloquent JavaScript. Going to have a lot to read, thank you! \$\endgroup\$ – npe Jun 27 '12 at 13:56
  • \$\begingroup\$ The first question of the day that made me laugh :) if you're playing about with inheritance, prototypes really aren't that difficult. I'd suggest you take a look into them. \$\endgroup\$ – David Barker Jul 5 '12 at 20:00
4
\$\begingroup\$

Q1: I wouldn't say it was good or particularly bad practice for some of the reasons I will go into below.

Q2: It's not memory efficient if you compare the code you have to a class / prototype based solution.

Q3: Take a look below, i've tried to keep it to as skeletal structure as possible to demonstrate how prototypes can be very useful.

xtend

xtend is great for smaller, intricate pieces of code and it has helped me loads recently. The caveat: In larger code bases you want to avoid the level of blind inheritance that xtend provides. Potentially in the future you may create a class that uses an object that has many layers of inheritance as an array of options for example. When that object squares up to the media and asked it's name and occupation, how does it know where all its components came from and what they do? In your code, how does the boy know he's a boy as well as a human for example.

Public / Private Methods

OOP in JS : Public / Private Variables will explain alot, if you are used to OOP in other languages this will probably click straight away.

Your first function is a class constructor with two private methods declared. I prefer to only set information in a constructor and then use public methods (prototypes) to deal with the workload.

Prototypes

Many objects can use prototypes and they're never duplicated within each object that is created.

Take this piece of code for example, first the class structure:

function CreateHuman(params) {
    // We create a new human object using params as initial data
    this.human = {
        name : params.name,
        age : params.age,
        gender : 'unknown'
    }
}

// first prototype, updates the gender with the one specified in the call
CreateHuman.prototype.setGender = function (gender) {
    this.human.gender = gender;
}

CreateHuman.prototype.setName = function (name) {
    this.human.name = name;
}    

CreateHuman.prototype.setAge = function (age) {
    this.human.age = age;
}

// this method does more work than any of the others
CreateHuman.prototype.setHobby = function() {

    // If the gender has been set, the hobby will be applied
    // You could use functions here as well

    switch(this.gender) {
        case('male') :
            this.person.hobby = 'football';
            this.person.hobbyActive = (this.age >= 6) ? true : false;
            break;
        case('female') :
            this.person.hobby = 'singing';
            this.person.hobbyActive = (this.age >= 4) ? true : false;
            break;
        deafult :
            this.person.hobby = 'no hobby';
            this.person.hobbyActive = true;
    }
}

// And a return method to get the object from the class
CreateHuman.prototype.getPerson = function() {
    return this.person;  // We return the object with all collected data
}

We could then use the class very easily to create a number of humans with different names, ages and genders.

var params = 
{
    name : "Dave",
    age : 30
};

I've included the params in this example as you may just need one object created and feeding it params is an easy way to make sure your code is useable by anyone else that might use your class.

var human = new CreateHuman(params);
var people = {}; // Make people an object to hold all the humans created

// Call our first prototype function
human.setGender('male');
// 2nd prototype
human.setHobby();

people.human1 = human.getPerson();

people.human1 if logged in the console would be:

Object {name : 'dave', 
        age : 30, 
        gender : 'male',
        hobby : 'football', 
        hobbyActive : true
       } 

Start again:

human.setName('Alexa');
human.setAge(28);
human.setGender('female');
human.setHobby();

people.human2 = human.getPerson()

// And so on

I hope that gives you an insight into prototypes, they will be far more useful to you than object inheritance (though I admit it does have its uses).

\$\endgroup\$
  • \$\begingroup\$ Thanks for the excellent explanation! But is it possible to easily do multiple inheritance with prototypes? I've found some examples (like this one) but they seam terribly complicated. \$\endgroup\$ – npe Jul 9 '12 at 7:38
  • \$\begingroup\$ @npe I'm sorry to say that JS just doesn't support multiple inheritance natively. If we think about it in detail, with no native support, There has to be a mechanism to copy / reference all parents prototypes, parents parents prototypes etc. and so on... it's never going to have an easy solution. \$\endgroup\$ – David Barker Jul 9 '12 at 10:33
1
\$\begingroup\$

The real problem with this is that if you create a lot of objects with this, each Human has its own copies of the getName() and getAge() functions, each Boy has his own copy of the playFootball() function, and each Girl has her own copy of the singASong() function.

The power of prototypes is that they allow multiple objects to share these functions, to share even the references to these functions, each carrying only a single reference to its prototype object, which then contains the references to these shared functions.

There is nothing inherently wrong with what you're doing. But it uses tremendously more memory than a prototype-based solution would. It's not good for situations where you want many thousands or millions of such objects.

\$\endgroup\$
1
\$\begingroup\$

If you try to make multiple inheritance in JavaScript, you should take a look at this library: Ring.js.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.