Search code examples
javascriptinheritanceencapsulation

Javascript inheritance and encapsulation, done efficiently


Coming from a C++ / Objective-C background, I'm trying to learn how to correctly and efficiently reproduce the patterns of inheritance and encapsulation in Javascript. I've done plenty of reading (Crockford etc.) and while there are plenty of examples of how to achieve one or the other, I'm struggling with how to combine them without introducing significant negatives.

At the moment, I have this code:

var BaseClass = (function() {

    function doThing() {
        console.log("[%s] Base-class's 'doThing'", this.name);
    }

    function reportThing() {
        console.log("[%s] Base-class's 'reportThing'", this.name);
    }

    return function(name) {
        var self = Object.create({});

        self.name = name;

        self.doThing = doThing;
        self.reportThing = reportThing;

        return self;
    }

}());

var SubClass = (function(base) {

    function extraThing() {
        console.log("[%s] Sub-class's 'extraThing'", this.name);
    }

    function doThing() {
        console.log("[%s] Sub-class's replacement 'doThing'", this.name);
    }    

    return function(name) {
        // Create an instance of the base object, passing our 'name' to it.
        var self = Object.create(base(name));    


        // We need to bind the new method to replace the old
        self.doThing = doThing;
        self.extraThing = extraThing;

        return self;
    }

}(BaseClass));

It mostly does what I want:

// Create an instance of the base class and call it's two methods
var base = BaseClass("Bert");

base.doThing();         // "[Bert] Base-class's 'doThing'"
base.reportThing();     // "[Bert] Base-class's 'reportThing'"

var other = BaseClass("Fred");


// Create an instance of the sub-class and call it's three methods (two from the base, one of it's own)
var sub = SubClass("Alfred");

sub.doThing();          // "[Alfred] Sub-class's replacement 'doThing'"
sub.extraThing();       // "[Alfred] Sub-class's 'extraThing'"
sub.reportThing();      // "[Alfred] Base-class's 'reportThing'"

But, there's (at least!) two issues:

  • I'm not convinced the prototype chain is intact. If I substitute a method in the prototype via one instance of a sub-class, other instances don't see it:
  • No encapsulation of .name property

I'm replacing the prototype's implementation of a function like this:

Object.getPrototypeOf(oneInstance).reportThing = function() { ... }
otherInstance.reportThing()    // Original version is still called

That's perhaps not a significant problem, but it is causing me to doubt my understanding.

Private variables is something I want to implement efficiently though. The module pattern of variable hiding doesn't help here, as it causes function definitions to exist per-object. I'm probably missing a way of combining patterns, so is there a way of achieving private variables without duplicating functions?


Solution

  • This is usually how I tackle inheritance and encapsulation in JavaScript. The defclass function is used to create a new class that doesn't inherit from any other class and the extend function is used to create a new class which extends another class:

    var base = new BaseClass("Bert");
    
    base.doThing();     // "Bert BaseClass doThing"
    base.reportThing(); // "Bert BaseClass reportThing"
    
    var sub = new SubClass("Alfred");
    
    sub.doThing();     // "Alfred SubClass replacement doThing"
    sub.extraThing();  // "Alfred SubClass extraThing"
    sub.reportThing(); // "Alfred BaseClass reportThing"
    
    var other = new SubClass("Fred");
    
    SubClass.prototype.reportThing = function () {
        console.log(this.name + " SubClass replacement reportThing");
    };
    
    other.reportThing(); // Fred SubClass replacement reportThing
    <script>
    function defclass(prototype) {
        var constructor = prototype.constructor;
        constructor.prototype = prototype;
        return constructor;
    }
    
    function extend(constructor, keys) {
        var prototype = Object.create(constructor.prototype);
        for (var key in keys) prototype[key] = keys[key];
        return defclass(prototype);
    }
    
    var BaseClass = defclass({
        constructor: function (name) {
            this.name = name;
        },
        doThing: function () {
            console.log(this.name + " BaseClass doThing");
        },
        reportThing: function () {
            console.log(this.name + " BaseClass reportThing");
        }
    });
    
    var SubClass = extend(BaseClass, {
        constructor: function (name) {
            BaseClass.call(this, name);
        },
        doThing: function () {
            console.log(this.name + " SubClass replacement doThing");
        },
        extraThing: function () {
            console.log(this.name + " SubClass extraThing");
        }
    });
    </script>

    Read the following answer to understand how inheritance works in JavaScript:

    What are the downsides of defining functions on prototype this way?

    It explains the difference between prototypes and constructors. In addition, it also shows how prototypes and classes are isomorphic and how to create “classes” in JavaScript.

    Hope that helps.