JavaScript inheritance in details

Overview

One of the most powerful features offered by object oriented programming (OOP) is inheritance (or ability to create subclasses of particular class).

On the one hand, JavaScript does not involve full-fledged support of classes such as one offered by C++ or Java. On the other hand, it supports constructors which are able to create objects with the use of executing code which allocates storage for the objects and initializes these objects (or part of them) by assigning initial values to their properties.

There are several possible ways to implement inheritance in JavaScript which are widely known and used. All of them have their own advantages and disadvantages.

However, there is an additional approach for implementing inheritance in JavaScript, which is supported by corresponding JavaScript library.

That library is named JSINER (originated from JavaScript INheritance supportER). Below we consider implementation of JavaScript inheritance proposed by JSINER as well as several other common ways of inheritance implementation in JavaScript.

"Lazy inheritance" is a design pattern used in JavaScript computer programming. It designates a "postponed linking" of an object with it's prototype (class) until it is needed.

If used properly, such approach may increase efficiency, simplicity and flexibility of OOP based code written using JavaScript.

Since lazy inheritance is called only for instance creation, it seems logical to combine process of class prototype creation with resolving necessary dependencies of that class.

The instances of objects in lazy inheritance are created in "mixed" mode — on first invocation, a factory is used to modify class prototype which is later used to create subsequent object instances.

The main features of "Lazy inheritance" approach are:

  • "Lazy inheritance" is prototype-based inheritance;
  • Maintaining proper order of script files declaration is not obligatory;
  • HTML page loading is faster, since there is no need in creation objects during page initialization and, additionally, some scripts could be loaded only at the moment in which they are actually necessary;
  • The parent classes for the object can be loaded dynamically after defining the object type;
  • Dependencies are declared in more explicit way; it's not necessary to group particular scripts into "modules" artificially and define dependencies between such "modules";
  • If lazy mode of scripts loading is used, only important scripts (those which are highly necessary for application functionality) will be loaded.

Here we start investigating patterns implemented by JSINER. But before digging into details, let's consider existing approaches used to implement inheritance.

Classical inheritance

JavaScript supports prototype-based inheritance. Each object constructor has a prototype property which is used to implement prototype-based inheritance and shared properties. Every object created by that constructor has an implicit reference to the prototype associated with its constructor. Such approach allows writing object oriented code in JavaScript.

The following fragment of code illustrates the common way to do this.

Let's assume that the following code is defined in person_cl.js JavaScript file and declares Person class

    // Person's constructor.
    function Person(aName)
    {
      this.fName = aName;
    }

    // Here we define function which returns
    // name of person and assign that function to
    // Person class prototype.
    Person.prototype.getName = function()
    {
       return this.fName;
    };

    // Here we declare function toString()
    // that returns string representation of
    // Person and assign that function
    // to Person class prototype
    Person.prototype.toString = function()
    {
       return "Person: " + this.getName();
    };

Let's declare another class, Employee which is inherited from Person one. Let's assume that declaration of that class is placed to employee_cl.js JavaScript file.

    // Employee constructor
    function Employee(aName, aUID)
    {
      this.fName = aName;
      this.fUID = aUID;
    }

    // The inheritance definition.
    // Here we create instance of Person and define
    // that is inherited from Person,
    // specify constructor for Employee and
    // add superClass property to Employee class 
    Employee.prototype = new Person();
    Employee.prototype.constructor = Employee;
    Employee.superClass = Person.prototype;

    // Here we declara function which returns UID
    // of employee and assing it to Employee class
    // prototype

    Employee.prototype.getUID = function()
    {
      return this.fUID;
    };

    // Here we redeclare definition of
    // toString() function which should be used by
    // Employee class. Our declaration calls function
    // from Person class and adds own Employee
    // related information
    
    Employee.prototype.toString = function()
    {
      var person = Employee.superClass.toString.call(this);
      return this.getUID() + ":" + person;
    };

And this is sample HTML page which uses declarations of JavaScript object above:

<html>
  <head>
    <title>Classical JavaScript inheritance test.</title>
    // Order of references to scripts
    // is important for proper code
    // execution.
    <script src="script/person_cl.js"></script>
    <script src="script/employee_cl.js"></script>
  </head>
  <body>
    <script language="JavaScript1.2">
       var employee = new Employee('John Doe', 1212);
       alert(employee);
     </script>
  </body>
</html>

Everything is seems to be quite straightforward in these examples so far.

However, such a "classical" implementation of inheritance has several disadvantages:

  • Definition of object inheritance spreads over several declarations;
  • The result of the inheritance depends on actual inheritance declaration order (i.e. which code will be executed earlier), because declaring inheritance is ordinary JavaScript operation of assignment;
  • Classical inheritance requires creation of object instance for binding objects;
  • The JavaScript modules (script statements) declaration should be properly organized in HTML document. If amount of scripts which are to be used by particular HTML page is small, this does not look as serious issue, but if amount of included scripts is significant, such a limitation could be quite a serious issue which forces one to have correct answers for several difficult questions. For example, which order of script embedding should be used if the document requires 50 modules? How will cross-references and dependencies affect declaration order?

Prototypal approach

There is another way of creating several objects with the same structure in JavaScript offered by Douglas Crockford. This approach assumes replacing prototype based inheritance for prototypal appoach using factory method instead of constructor. Such a factory method is responsible for creation of new object instance.

Generally speaking, such approach does not represent pure inheritance, since it is oriented to creation JavaScript instances with the same properties, rather than classes.

However, it is intended to solve tasks which are close to ones solved by inheritance, and that's why we consider it there.

The following code snippets illustrate prototypal approach in more details. We declare the same example classes - Person and Employee.

First, define Person using prototypal approach. Let's assume that declaration of that class is placed to person_pp.js JavaScript file.

    // First, we define function which represents
    // a factory for creation of Person objects
    function Person(aName)
    {
      var self = PROTOTYPAL.object();
      self.fName = aName;
      self.toString = Person.toString;
      self.getName = Person.getName;
      return self;
    }

    // Here we declare function which
    // will return name of Person and
    // adding it to Person object
    Person.getName = function()
    {
       return this.fName;
    };

    // Here we declare function which returns
    // representation of the Person object and
    // assign it to Person object
    Person.toString = function()
    {
       return "Person: " + this.getName();
    };


Let's declare another class, Employee which is inherited from Person one. Let's assume that declaration of that class is placed to employee_pp.js JavaScript file.

  // Here we define factory which will be used to
  // create Employee that extend Person
  function Employee(aName, aUID)
  {
    // here we call method from library (prototypal.js)
    // which supports prototypal inheritance
    var self = PROTOTYPAL.object( Person(aName) );
    self.fUID = aUID;
    self.getUID = Employee.getUID;
    self.toString = Employee.toString;
    return self;
  }

  // Here we declare function which returns
  // representation of the Employee object and
  // assign it to Employee object
  Employee.toString = function()
  {
    return this.getUID() + ":" + this.getName();
  };

  // Here we declare function which
  // will return UID of Employee and
  // adding it to Employee object
  Employee.getUID = function()
  {
    return this.fUID;
  };


And this is sample HTML page which uses declarations of JavaScript object above:

<html>
  <head>
    <title>Prototypal JavaScript inheritance test.</title>
    // We also need to include base prototypal
    // related code
    <script src="script/prototypal.js"></script>
    // Order of references to scripts
    // is important for proper code
    // execution.
    <script src="script/person_pp.js"></script>
      <script src="script/employee_pp.js"></script>
  </head>
  <body>
    <script language="JavaScript1.2">
       var employee = new Employee('John Doe', 1212);
       alert(employee);
     </script>
  </body>
</html>

Comparing to "classical" inheritance, the prototypal based approach has significant advantages, but also several significant drawbacks:

  • As soon as object instance is created, and later it's necessary to alter its structure (i.e. set of properties and methods) this could be done online on individual object level. In other words, any changes which could be performed with object prototype does not affect objects crated on that prototype at all.

    Contrary, if "classical inheritance" is used for altering structures of ALL created instances, it is simply enough to change corresponding prototype.

    Actually, for some types of application this can be unnecessary or inapplicable; however, it is extremely useful for some other types of applications to have such functionality because it allows implementing basic (or framework) logic without worrying about how it will be adapted for particular application.

    Support of classical inheritance is extremely important if class is created using aggregation (i.e. class has properties which are classes too) and it's necessary to change structure not only for that class, but also structure of classes which are aggregated by that class.
  • Another drawback of prototypal approach — all objects returned by factory are untyped, and it's less convenient to work with them comparing to typed objects;
  • And finally, prototypal approach requires that parent should be already created before definition of child object (in other words, referring to example above, at the moment of Employee object definition, the JavaScript runtime should be able to create instance of Person).
  • The same as for "classical inheritance", it is required to maintain proper order of script files declaration to have the entire inheritance scheme working.

JSINER' Inheritance approach

JSINER' offers approach which simplifies writing object oriented code with support of prototype based inheritance. Unlike of "classical" approach, it represents a "Lazy inheritance" because reference to parent class is invoked only at the moment of object instance creation.

Basically, it's possible to say that instances of objects in such approach are created in "mixed" mode — on first invocation, an appropriate factory is used to modify object constructor prototype which is later used for subsequent object instances creation.

Moreover, since "Lazy inheritance" is called only once at the moment of first object instance creation, it becomes possible to combine process of class prototype creation with resolving necessary dependencies of that class. In other words, the process of prototype construction also allows loading scripts (if ones were not loaded before) which particular class depends on.

Such approach to inheritance support has the following benefits:

  1. "Lazy inheritance" has the same benefits as classical JavaScript prototype features;
  2. It is not necessary to maintain proper order of script files declaration (contrary to "classical inheritance");
  3. In most cases, HTML page which contains JavaScript based on JSINER is loaded faster since there are no objects created during page initialization (contrary to prototypal and classical approach) and since some scripts could be loaded only at the moment in which they are actually necessary (they are necessary if the caller code during it's execution really creates instances of corresponding classes);
  4. Dependencies are declared in more explicit way and class depends only on concrete scripts; Moreover, it's not necessary to artificially group particular scripts into "modules" and define dependencies between such "modules";
  5. If lazy mode of scripts loading is used, only necessary scripts (which are actually required for application functionality) will be loaded. If, for example, some HTML page may operate in different modes, like "read-write" and "read-only", for every mode required scripts will be loaded and this will be performed automatically without manual script loading optimization;

The following code snippets illustrate JSINER approach in more details. Again, we declare the same example classes - Person and Employee.

First, we define Person. Let's assume that declaration of that class is placed to person.js JavaScript file.

  // Constructor for Person class
   function Person(aName)
   {
     this.fName = aName;
   }

   // Function which returns name of Person's.
   // That function is assigned to prototype of
   // Person class similarly to "classical"
   // inheritance.
   Person.prototype.getName = function()
   {
     return this.fName;
   };

   // Here we define function which returns
   // string representation of Person
   // assign it to prototype of Person
   // class (again, similar to classical
   // approach)
   Person.prototype.toString = function()
   {
     return "Person: " + this.getName();
   };

No we illustrate how Person may be extended using couple of examples.

Example 1 - "inheritance only".

This example is quite basic and illustrates only the way how "pure inheritance" should be supported via JSINER (without resolving dependencies between classes and required script files).

Here we assume that the code below is placed to employee1.js file.

  // Here we define constructor for Employee
  // class and declare that Employee class
  // inherits Person one
  function Employee(aName, aUID)
  {
    // lazy inheritance calling
    var self = JSINER.extend(this, "Person");
    self.fName = aName;
    self.fUID = aUID;
    return self;
  }

  // Here we define function which return
  // UID of employee and assign it to
  // prototype of Employee class.
  // This is exact approach as one used by
  // classical inheritance
  Employee.prototype.getUID = function()
  {
    return this.fUID;
  };

  // Here we define function which
  // returns string representation of
  // Employee object and assign it
  // to Employee class prototype
  // (as if classical approach is used).
  // Our implementation combines
  // string representation of Person object
  // and own data from Employee class
  Employee.prototype.toString = function()
  {
     var person = Employee.superClass.toString.call(this);
     return this.getUID() + ":" + person;
  };


And this is sample HTML page which uses declarations of JavaScript object above:

<html>
  <head>
    <title>JSINER lazy JavaScript inheritance test.</title>
    // We need to include reference to
    // JSNIER code
    <script src="script/jsiner.js"></script>
    // Now it's not necessary to
    // maintain order of scripts loading
    // manually.
    <script src="script/employee1.js"></script>
    <script src="script/person.js"></script>
  </head>
  <body>
    <script language="JavaScript1.2">
       var employee = new Employee('John Doe', 1212);
       alert(employee);
     </script>
  </body>
</html>

During execution of example of code listed above, lazy inheritance is called only once for first creation of Employee instance. It is not necessary to keep proper order of script files declaration in HTML document.

Example 2 - "inheritance and resolving references"

This example illustrates more advanced features of JSINER libarary, such as automatic resolving of references to particular JavaScript files in which appropriate JavaScript classes are defined.

Here we assume that the code below is placed to employee2.js file.

  // Here we inform JSINER that Employee class depends
  // on person script. Actually, the "person"
  // string does not represent a script name, but
  // rather some key which is associated with
  // actual script. JSINER allows specifying
  // mapping functionality which is responsible
  // to determine actual name of script using
  // given key.
  // However, for simplicity of examples,
  // here it's assumed that resolver considers
  // key of script as name of script file.
  JSINER.addDependency( {Employee:"person"} );


  // Exactly as in example 1, we define constructor
  // for Employee class. In general, all code
  // below is the same as one used in
  // Example 1
  function Employee(aName, aUID)
  {
    var self = JSINER.extend(this, "Person");
    self.fName = aName;
    self.fUID = aUID;
    return self;
  }

  Employee.prototype.getUID = function()
  {
    return this.fUID;
  };

  Employee.prototype.toString = function()
  {
     var person = Employee.superClass.toString.call(this);
     return this.getUID() + ":" + person;
  };


This is sample HTML page which uses declarations of JavaScript object for Example 2:

<html>
  <head>
    <title>JSONER lazy JavaScript inheritance test.</title>
    // We need to include reference to
    // JSNIER code
    <script src="script/jsiner.js"></script>
    // Note that we include reference
    // to script that contains Employee class only
    <script src="script/employee2.js"></script>
    // And no it's not required
    // to reference person.js script explicitely -
    // JSINER will resolve and load it automatically
    <!--<script src="script/person.js"></script>-->
  </head>
  <body>
    <script language="JavaScript1.2">
       var employee = new Employee('John Doe', 1212);
       alert(employee);
     </script>
  </body>
</html>

During execution of example code listed above, linkage of Employee and Person as well as loading of necessary script "person.js" is performed automatically with the first creation of Employee instance.

As it is illustrated by examples above, proposed "Lazy inheritance" pattern is similar to Prototypal approach, but:

  • It is called only once during first creation of object instance;
  • Linkage of objects and scripts is performed automatically;
  • To link classes the prototype based inheritance is used;

In general, the proposed scheme considers "lazy" loading as primary mode for loading particular scripts. However, this is not strict requirement — it is also possible to force loading of all scripts which form particular application before starting objects creation if this way of loading is required by particular application architecture.

Since "lazy" inheritance approach is non-intrusive, it could be used in combination with other inheritance pattern. However, it's necessary to remember that simultaneous use of several types of inheritance within the same project or application can be very confusing and non-convenient for development, support and maintenance.

JSINER' Internals

Here hightlight some details of inheritance and dependencies resolving in JSINER library.

"Lazy inheritance" is invoked within the Object' constructor code and is called only once for first object instance creation.

After invocation, the following steps are performed by JSINER library:

  • First, the algorithm tests Object' dependencies;
  • If Object' dependency had been registered before the object constructor was called (interrelations can be defined in the object constructor too), corresponding JavaScript dependencies are loaded.
  • Then, if the class, from which the object inherits, is defined and it is not a default one (Object), Object's constructor prototype is modified by classical schema of prototype-based inheritance.
  • After completing prototype modification, constructor of the Object' is called again to apply changes to new instance of the Object.

There steps are illustrated by diagram below. Please click on the image to enlarge.

JSINER inheritance stucture

The derived instance of the Object may be modified in the constructor according to particular logic. For example, some properties of the object may be defined.

The resulting object is the same JavaScript object as it would be created with classical prototype-based inheritance.

Conclusion

As the matter of thumb, there is no "silver bullet" solution and proposed approach does not pretend to be universal way of implementing inheritance in JavaScript.

Of course, choice of inheritance type declaration is dictated by particular needs and specifics of particular application.

However, the approach described there has several significant advantages that allow simplifying of writing object oriented code in JavaScript.



  SourceForge.net Logo   Support This Project