Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

javascript inheritance pattern comparison

I have read through some tutorials about javascript prototypal inheritance patterns but I am not sure which is the best practice out of the following two. I noted that many people do this inheritance pattern:

var A = function (){}
A.prototype = {} 

var B = function () {
    A.apply(this, arguments); // Calling the constructor of A
}
B.prototype = new A(); // Inherit from A through an instance

Alternative, there are some sources that do the following pattern instead:

var A = function (){}
A.prototype = {} 

var B = function () {
    A.apply(this, arguments); // Calling the constructor of A
}
for (var prop in A.prototype) {
    B.prototype[prop] = A.prototype[prop]; // Inherit from A by copying every property/methods from A
}

Although both patterns work, I rarely see people use the latter inheritance pattern (ie. copying each property/methods from the parent's prototype) - why? Is there something wrong with copying properties/methods directly from parent to child? Also, are these two patterns intrinsically different in some ways?

Thank you.

like image 244
tonytz Avatar asked Dec 16 '22 01:12

tonytz


2 Answers

These patterns are very different, and as you may have guessed, the first is better (but not the best possible). Let us compare:

The Most Modern, Best Pattern

B.prototype = Object.create(A.prototype);

This uses the Object.create function to set B.prototype to a new object, whose internal [[Prototype]] is A.prototype. This is basically exactly what you want: it will make B instances delegate to A.prototype when appropriate.

The Original Pattern

B.prototype = new A();

This was how things used to be done, before ES5's Object.create came about. (Although there were workarounds, even if they were not widely used.) The problem with this approach is that any instance-only data properties also end up on B.prototype, which is not desired. Additionally, any side effects of calling A's constructor will happen. Essentially this approach muddles two related, but different concepts: object instantiation, and object construction.

The Non-Standard Pattern from Your Post

for (var prop in A.prototype) {
    B.prototype[prop] = A.prototype[prop];
}

This pattern has several problems:

  • It will copy properties from everywhere in A's prototype chain, all the way down to Object, directly into B.prototype. This defeats much of the purpose of prototypal inheritance, wherein you should be delegating up the prototype chain, instead of squashing it into one single level.
  • It only gets enumerable properties from A.prototype and its prototype chain, since for ... in skips non-enumerable properties.
  • It messes up any getters or setters defined on A.prototype (or its prototype chain), simply copying over their value instead of the getter/setter functions.
  • It will look effin' weird to anyone trying to read your code.
like image 55
Domenic Avatar answered Jan 03 '23 06:01

Domenic


Here is another method using the factory pattern, no prototypes:

/* parent */
function Animal(name, legs)
{
    /* members and methods declared within a new object */
    var obj =
    {
        name: name,
        legs: legs,
        description: function ()
        {
            return this.name + " is an animal";
        },
        printNumLegs: function ()
        {
            return this.name + " has " + this.legs + " legs";
        }
    }

    return obj;
}

/* subclass */
function Cat()
{
    /* apply parent arguments in the context of the current object */
    var obj = Animal.apply(this, arguments);

    /* public member */
    obj.furColor = "black";
    /* private member */
    var sleeping = false;
    /* private method */
    function printSleepingState()
    {
        /* can access public member ('name') without it being passed as constructor argument */
        return obj.name + " is " + (sleeping ? "sleeping" : "not sleeping");
    }

    /* declare a new method */
    obj.printFurColor = function ()
    {
        return obj.name + " has " + obj.furColor + " fur";
    }

    /* overloading */

    /* save parent method if needed */
    var oldDescription = obj.description;

    obj.description = function ()
    {
        return oldDescription.apply(obj) + " and " + printSleepingState();
    }

    return obj;
}

/* create object without new */
var kitty = Cat("Kitty", 4);
kitty.furColor = "yellow";

There is no "best" method anyway...it's all a matter of taste.

like image 29
mihai Avatar answered Jan 03 '23 06:01

mihai