1

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?

Chris
  • 3,445
  • 3
  • 22
  • 28
  • "I'm trying to learn how to correctly and efficiently reproduce the patterns of inheritance and encapsulation in Javascript" - don't assume that the patterns that you know from C++ will work effectively in JavaScript. – joews Feb 17 '15 at 09:31
  • @joews I'm not assuming there'll be a 1:1 correspondence, but as it's definitely possible to incorporate the two patterns individually, I'm hopeful that there's a clean way of combining them. – Chris Feb 17 '15 at 09:38
  • To me this is one of the most annoying aspects of JavaScript, that so many people want to enforce their Java / C++ habits on it. – mvw Feb 17 '15 at 12:09
  • It's not that I want to preserve a habit as such. I'm quite happy for the code to be structured in whatever way is best, but I don't see the downside of any of the concepts I'm looking to emulate. – Chris Feb 17 '15 at 14:26

3 Answers3

1

The simple recipe follows:

function BaseClass(someParams)
{
   // Setup the public properties, e.g.
   this.name = someParams.name;
}

BaseClass.prototype.someMethod = function(){
   // Do something with the public properties
}

Now the inheritance occurs this way

function SubClass(someParams)
{ 
    // Reuse the base class constructor
    BaseClass.call(this, someParams);

    // Keep initializing stuff that wasn't initialized by the base class
    this.anotherProperty= someParams.anotherProperty;
}

// Copy the prototype from the BaseClass
SubClass.prototype = Object.create(BaseClass.prototype);
SubClass.prototype.constructor = SubClass;

// Start extending or overriding stuff
SubClass.prototype.someMethod = function(){

   // In case you still wanna have the side effects of the original method
   // This is opt-in code so it depends on your scenario.
   BaseClass.prototype.someMethod.apply(this, arguments);

   // Override the method here       
}

Taken from: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript

P.S. Object.create may not be supported on all old browsers, but don't worry, there's a polyfill for that in this link. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create

Adrian Salazar
  • 5,279
  • 34
  • 51
  • In Java when you override a function, you typically call the super function too. You can do the same with Javascript: https://gist.github.com/StephanBijzitter/b2495ebf9702b3866c91 – Stephan Bijzitter Feb 17 '15 at 12:45
  • That's opt-in, so, nothing keeps you from using "call" or "apply" on BaseClass.prototype.someMethod. I'll update the code to reflect this. Thanks... – Adrian Salazar Feb 17 '15 at 13:43
  • Showing how to call methods on the super class is going to be useful, thanks. – Chris Feb 17 '15 at 14:37
1

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.

Community
  • 1
  • 1
Aadit M Shah
  • 72,912
  • 30
  • 168
  • 299
  • Nicely contained class definitions - I like it. Very reminiscent of the Mootools approach. – Chris Feb 17 '15 at 14:38
  • Indeed. In addition, the `defclass` and `extend` functions are just three lines of code each. – Aadit M Shah Feb 17 '15 at 15:03
  • An elegant solution but does this solve the data hiding issue though? I can still call other.name to return 'Fred', thus I do not require accessor methods. I still haven't found a solution to this problem. Perhaps ECMA6 solves this, but ECMA5 and below don't offer data hiding. This means that any variable that is defined on the base class can be accessed from any derived object... eeek! – miller the gorilla Feb 16 '16 at 14:12
0

If you want to preserve the prototype chain, you must override and use .prototype: Example: Main Class:

function BaseClass(){

}
BaseClass.prototype.doThing = function(){...}

SubClass:

function SubClass(){
}
SubClass.prototype= new BaseClass();
SubClass.prototype.extraThing = function(){};

Now, whenever you change extraThing or doThing it gets replaced everywhere. The name property is accessible as a public variable (it's not static).

If you want it static, you must put it in prototype.

If you want it private, you mast make it function local:

function BaseClass(nameParam){
 var name = nameParam;
}

To create an object simply call the function:

var testObj = new BaseClass("test");
testObj.doThing();

If you want to combine private variables with rewritable functions, you might find your answer here. But if you are able to rewrite the function that has access to the private variable, it's not really a private variable anymore.

Bogdan.Nourescu
  • 905
  • 6
  • 17
  • I was under the impression that 'new' wasn't considered the correct approach these days and was expecting Object.create(base) to take care of the prototype chain. My understanding clearly needs work. – Chris Feb 17 '15 at 14:21
  • The problem isn't that you shouldn't use new, the problem is if you forget the "new" for creating a new instance – Bogdan.Nourescu Feb 17 '15 at 14:36