Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Crockford's Prototypical Inheritance - Usage

I've been building a small JS framework for use at my job, and I'd like to employ Douglas Crockford's prototypical inheritance patterns. I think I get the general idea of how the prototype object works, but what isn't clear is the way in which I would use this pattern beyond the simplest example.

I'll flesh it out to the point that I understand it.

(function () {

    'use strict';

    var Vehicles = {};

    Vehicles.Vehicle = function () {
        this.go = function () {
            //go forwards
        };

        this.stop = function () {
            //stop
        };
    };

    Vehicles.Airplane = Object.create(Vehicles.Vehicle());

}());

So now my Vehicles.Airplane object can go() and stop(), but I want more. I want to add takeOff() and land() methods to this object. I could just use ugly dot notation afterwards:

Vehicles.Airplane.takeOff = function () {
    //take off stuff
}

But that seems wrong, especially if I were to add many methods or properties. The question asked at here seems to be very similar to mine, but the answer doesn't quite ring true for me. The answer suggests that I should build an object literal before using Object.create, and that I should pass that object literal into the create method. In the example code given, however, it looks like their new object inherits nothing at all now.

What I'm hoping for is some syntax similar to:

Vehicles.Airplane = Object.create(Vehicles.Vehicle({
    this.takeOff = function () {
        //takeOff stuff
    };
    this.land = function () {
        //land stuff
    };
}));

I know this syntax will break terribly with Object.create right now, because of course I'm passing Vehicle.Vehicle a function rather than an object literal. That's beside the point. I'm wondering in what way I should build new properties into an object that inherits from another without having to list them out one at a time with dot notation after the fact.


EDIT:

Bergi, after some anguished thought on the topic, I think I really want to go with what you described as the "Classical Pattern". Here is my first stab at it (now with actual code snippets rather than mocked up hypotheticals - You even get to see my crappy method stubs):

CS.Button = function (o) {
    o = o || {};

    function init(self) {
        self.domNode = dce('a');
        self.text = o.text || '';
        self.displayType = 'inline-block';
        self.disabled = o.disabled || false;

        self.domNode.appendChild(ctn(self.text));
        if (o.handler) {
            self.addListener('click', function () {
                o.handler(self);
            });
        }
    }

    this.setText = function (newText) {
        if (this.domNode.firstChild) {
            this.domNode.removeChild(this.domNode.firstChild);
        }
        this.domNode.appendChild(ctn(newText));
    };

    init(this);
};
CS.Button.prototype = Object.create(CS.Displayable.prototype, {
    constructor: {value: CS.Button, configurable: true}
});

CS.Displayable = function (o) { // o = CS Object
    o = o || {};

    var f = Object.create(new CS.Element(o));

    function init(self) {
        if (!self.domAnchor) {
            self.domAnchor = self.domNode;
        }
        if (self.renderTo) {
            self.renderTo.appendChild(self.domAnchor);
        }
    }

    //Public Methods
    this.addClass = function (newClass) {
        if (typeof newClass === 'string') {
            this.domNode.className += ' ' + newClass;
        }
    };
    this.addListener = function (event, func, capture) {
        if (this.domNode.addEventListener) {
            this.domNode.addEventListener(event, func, capture);
        } else if (this.domNode.attachEvent) {
            this.domNode.attachEvent('on' + event, func);
        }
    };
    this.blur = function () {
        this.domNode.blur();
    };

    this.disable = function () {
        this.disabled = true;
    };

    this.enable = function () {
        this.disabled = false;
    };

    this.focus = function () {
        this.domNode.focus();
    };

    this.getHeight = function () {
        return this.domNode.offsetHeight;
    };

    this.getWidth = function () {
        return this.domNode.offsetWidth;
    };

    this.hide = function () {
        this.domNode.style.display = 'none';
    };

    this.isDisabled = function () {
        return this.disabled;
    };

    this.removeClass = function (classToRemove) {
        var classArray = this.domNode.className.split(' ');
        classArray.splice(classArray.indexOf(classToRemove), 1);
        this.domNode.className = classArray.join(' ');
    };

    this.removeListener = function () {
        //Remove DOM element listener
    };

    this.show = function () {
        this.domNode.style.display = this.displayType;
    };

    init(this);
};
CS.Displayable.prototype = Object.create(CS.Element.prototype, {
    constructor: {value: CS.Displayable, configurable: true}
});

I should be quite clear and say that it's not quite working yet, but mostly I'd like your opinion on whether I'm even on the right track. You mentioned "instance-specific properties and methods" in a comment in your example. Does that mean that my this.setText method and others are wrongly placed, and won't be available to descendant items on the prototype chain?

Also, when used, it seems that the order of declaration now matters (I can't access CS.Displayable.prototype, because (I think) CS.Button is listed first, and CS.Displayable is undefined at the time that I'm trying to reference it). Is that something I'll just have to man up and deal with (put things in order of ancestry in the code rather than my OCD alphabetical order) or is there something I'm overlooking there as well?

like image 243
beejay Avatar asked Jan 10 '13 21:01

beejay


People also ask

What is prototypical inheritance and how useful is it?

Prototypical inheritance allows us to reuse the properties or methods from one JavaScript object to another through a reference pointer function. All JavaScript objects inherit properties and methods from a prototype: Date objects inherit from Date.

What languages use prototypal inheritance?

Prototypal inheritance is a form of object-oriented code reuse. Javascript is one of the only [mainstream] object-oriented languages to use prototypal inheritance. Almost all other object-oriented languages are classical. In classical inheritance, the programmer writes a class, which defines an object.

How does the prototypical inheritance differ from the class inheritance?

The most important difference between class- and prototype-based inheritance is that a class defines a type which can be instantiated at runtime, whereas a prototype is itself an object instance.

What is prototype and 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.


1 Answers

Vehicles.Airplane = Object.create(Vehicles.Vehicle());

That line is wrong. You seem to want to use new Vehicles.Vehicle - never call a constructor without new!

Still, I'm not sure which pattern you want to use. Two are coming to my mind:

Classical Pattern

You are using constructor functions just as in standard JS. Inheritance is done by inheriting the prototype objects from each other, and applying the parent constructor on child instances. Your code should then look like this:

Vehicles.Vehicle = function () {
    // instance-specific properties and methods,
    // initialising
}
Vehicles.Vehicle.prototype.go = function () {
     //go forwards
};
Vehicles.Vehicle.prototype.stop = function () {
    //stop
};

Vehicles.Airplane = function() {
    // Vehicles.Vehicle.apply(this, arguments);
    // not needed here as "Vehicle" is empty

    // maybe airplane-spefic instance initialisation
}
Vehicles.Airplane.prototype = Object.create(Vehicles.Vehicle.prototype, {
    constructor: {value:Vehicles.Airplane, configurable:true}
}); // inheriting from Vehicle prototype, and overwriting constructor property

Vehicles.Airplane.prototype.takeOff = function () {
   //take off stuff
};

// usage:
var airplane = new Vehicles.Airplace(params);

Pure Prototypical Pattern

You are using plain objects instead of constructor functions - no initialisation. To create instances, and to set up inheritance, only Object.create is used. It is like having only the prototype objects, and empty constructors. instancof does not work here. The code would look like this:

Vehicles.Vehicle = {
    go: function () {
         //go forwards
    },
    stop: function () {
         //stop
    }
}; // just an object literal

Vehicles.Airplane = Object.create(Vehicles.Vehicle); // a new object inheriting the go & stop methods
Vehicles.Airplane.takeOff = function () {
   //take off stuff
};

// usage:
var airplane = Object.create(Vehicles.Airplane);
airplane.prop = params; // maybe also an "init" function, but that seems weird to me
like image 184
Bergi Avatar answered Oct 15 '22 02:10

Bergi