Friday, March 29, 2024

Emulate Classical Inheritance in JavaScript

I recently wrote two articles about JavaScript Ptototypal inheritance. The first one, titled JavaScript Prototypical Inheritance Explained described how to use a JavaScript Object’s prototype property to extend it, either by adding new functionality or by modifying existing methods. The second article, called JavaScript Object Chaining using Prototypal Inheritance, established how inheritance is implemented using a Prototypal framework. While Prototypal inheritance works well enough, it leaves a lot of developers wishing that they could just use the more traditional classical inheritance that is more typically associated with OO programming. Authors of JS libraries have responded by including at least one module that emulated familiar OO constructs. We’ll be picking and choosing some of the best ideas from various popular JS libraries to implement our own Classical Inheritance model.

Features of Classical Inheritance

Before we get into the code, let’s examine some of the most common features of Classical Inheritance that a library might want to emulate. In doing so, the inevitable question arises as to which language style are we talking about? Inheritance rules are not the same accross all OO languages. For instance, C++ supports multiple inheritance, while Java does not. Most people automatically think of Java when looking to port syntax and constructs over to JavaScript, and that assumption has been borne out with most JS libraries. Nonetheless, some borrow liberally from other languages, such as Ruby, Smalltalk, and Perl. Here are some Java inheritance rules that we may want to apply to our own “Classes”:

  • All classes, except the Object class, must inherit from exactly one other class.
  • The Object class is the super class of all other classes.
  • In Java, the Object class implements basic toString(), equals(), hashCode(), clone(), and getClass() methods that subclasses *may* override.
  • Instance variable and methods can be public, private, or protected (member hiding).
  • Functionality contracts are enforced via interfaces.
  • Methods and classes can be defined as abstract and/or final.
  • We can get at Class methods and properties using Reflection.

And last but not least, all of these features must be easy enough to use that developers don’t curse the day the JavaScript or OOP was invented!

What a Class Might Look Like in JavaScript

Our first step is to create a means of defining an Object using the familiar Class template. With that in mind, here’s a simple Person class in Java:

public Class Person { 
  String name = "john Doe";
  int    age  = 0;
 
  public Person(String name, int age)
  {
    this.name = name;
    this.age  = age;
  }

  //getters and setters
  public String function getName()     { return this.name; }
  public void   function setName(name) { this.name = name; }
  
  //public methods
  public String function addBirthday() { this.age++; }
  public String function toString()    { return "My name is "+this.name+" and I am "+this.age+" years old."; }
}

Here then is a reasonable facsimile in JavaScript using a Class() function that accepts an Object literal as an argument:

var Person = Class({ 
    initialize: function(name, age) {
                this.name = name;
                this.age  = age;
    },
    //defaults
    age:  0,
    name: 'John Doe',
    
    //getters and setters
    getName = function ()     { return this.name; },
    setName = function (name) { this.name = name; },
    
    //public methods
    addBirthday: function()   { this.age++; },
    toString: function()      { return "My name is "+this.name+" and I am "+this.age+" years old."; }
}); 

//create an instance of a person
var rob = new Person("Rob", 29); //still 29! (I wish!)

Extending a Class

In Java, classes are extended using the “extends” keyword:

public Class Employee extends Person {

Most JavaScript developers differ in their implementation of Class extension, but there are a couple of popular approaches. One is to give the Class constructor function a method called extend() that passes on inherited properties and methods along to the child. Here is an example, taken from a library called JS Class:

var User = new JS.Class({    
  initialize: function(name) { this.username = name; }
});

User.extend({    
  find: function(id) {        
    // Return a User with id   
  },    
  create: function(name) { return new this(name); }
});

Another good approach is to simply allow for an additional argument to the Class() function that specifies the parent:

var LoudUser = new JS.Class(User, {    
  extend: {        
    create: function(name) { return this.callSuper(name.toUpperCase()); }    
  }
});

The above code also demonstrates how Employee instances can access the methods of its parent using the “super.” prefix:

While we can’t use it as is because “super” is a reserved word in JavaScript, we can use a similarly named prefix like “$uper”, “$super”, or, as in the example below, “callSuper”:

I like the simplicity of that last example, even though it does not explicitly use the term “extends” (a Class.extends() function could be made to accept the exact same two arguments of parent and the child class object). Here’s a JS Employee Class that calls the Class function with the extra parent argument and provides an initialize() constructor that passes on the name and age – as an array – to the parent constructor :

var Employee = Class(Person, { 
    initialize: function(name, age, id) {  
      this.$super('initialize', [name, age]);
      this.id = id;
    },
    toString: function() { 
      return "I an employee #"+this.id+". "+ this.$super('toString');
    }
});

var theBoss = new Employee('Bob', 40, '12345');

Going Forward

That covers the Class definition aspect of classical inheritance in JavaScript. The next task will be to come up with the Class() code that will not only create our objects, but pass on inherited Class members as well. In doing so, we will learn about the “JScript DontEnum” bug and method borrowing.

Robert Gravelle
Robert Gravelle
Rob Gravelle resides in Ottawa, Canada, and has been an IT guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Popular Articles

Featured