Lazy Inheritance
From Ajax Patterns
Contents |
Ajax/Javascript Programming and Usability in "Ajax Design Patterns" Book |
Overview
Object oriented programming (OOP) offers the powerful feature of inheritance, or the ability to create subclasses from a particular class. JavaScript does not provide full-fledged support of classes such as those available in C++ or Java. However, it does support constructors, which create objects that can execute code allocating storage for the objects, as well as initializing them by assigning initial values to their properties.
There are several ways to implement inheritance in JavaScript. Each has its own advantages and disadvantages.
Lazy Inheritance design pattern
"Lazy inheritance" is a JavaScript design pattern in which the linking of an object with its prototype (class) is postponed until needed. In contrast to a "classical" approach, reference to an object's parent class is invoked when the object instance is created. This approach can increase the efficiency, simplicity, and flexibility of OOP-based JavaScript.
One might say that instances of objects using this approach are created in "mixed" mode- when first invoked, an appropriate factory modifies the object constructor prototype which will create subsequent object instances. Because "Lazy inheritance" is called only once, when the first object instance is created, it is possible to combine class prototype creation with the task of resolving that class's necessary dependencies. Constructing a prototype also allows loading scripts which that particular class requires.
This approach to inheritance support has the following benefits:
- "Lazy inheritance" has the same benefits as classical JavaScript prototype features;
- It is not necessary to declare script files in a certain order;
- In most cases, an HTML page loads faster since there are no objects created during page initialization; some scripts will load only when they are actually necessary, such as when the caller code creates instances of relevant classes;
- Dependencies are declared in a more explicit way, and class depends only on concrete scripts. It's unnecessary to group particular scripts into "modules" and define dependencies between such "modules";
- If a "lazy" mode of script loading is used, only the scripts required for the application's functionality will be loaded initially. For example, if an HTML page operates in different modes, such as "read-write" and "read-only", only the scripts required for each mode will be loaded.
Before discussing "lazy inheritance" further, let's consider some other approaches for implementing inheritance.
Classical prototype-based inheritance
JavaScript supports prototype-based inheritance, which means that each object constructor has a prototype property which can implement shared properties. Every object created by an object constructor has an implicit reference to its constructor's prototype. Using an object constructor and its prototype property allows writing object-oriented code in JavaScript.
The code fragment below illustrates a common way to use prototype-based inheritance.
Let's assume that the following code is defined in a JavaScript file, person_cl.js, and declares the Person class:
/**
* Person’s constructor.
*/
function Person(aName)
{
this.fName = aName;
}
/**
* Here we define a function, getName(), which returns the name of a person. We assign that function to
* the Person class prototype.
*/
Person.prototype.getName = function()
{
return this.fName;
};
/**
* Here we declare the function toString() which returns a string representation of
* Person. We assign that function to the Person class prototype as well.
*/
Person.prototype.toString = function()
{
return "Person: " + this.getName();
};
Let's declare another class, Employee, which is inherited from Person #1. Let's assume the declaration of that class is placed in the file employee_cl.js:
/**
* Employee constructor
*/
function Employee(aName, aUID)
{
this.fName = aName;
this.fUID = aUID;
}
/*
* The inheritance definition.
* Here we create an instance of Person which inherits from Person,
* specify the constructor for Employee, and add a superClass property to the Employee class
*/
Employee.prototype = new Person();
Employee.prototype.constructor = Employee;
Employee.superClass = Person.prototype;
/**
* Here we declare a function which returns the UID of an employee,
* and assign the function to the Employee class prototype
*/
Employee.prototype.getUID = function()
{
return this.fUID;
};
/**
* Here we redeclare our definition of the toString() function which should be used by
* the Employee class. Our declaration calls the function from the Person class and adds its own Employee
* related information
*/
Employee.prototype.toString = function()
{
var person = Employee.superClass.toString.call(this);
return this.getUID() + ":" + person;
};
Here is a sample HTML page which uses the JavaScript objects declared above:
<html>
<head>
<title>The Classical JavaScript inheritance test.</title>
<!-- Order your references to scripts carefully to ensure proper code execution -->
<script src="script/person_cl.js"></script>
<script src="script/employee_cl.js"></script>
</head>
<body>
<script>
var employee = new Employee('John Doe', 1212);
alert(employee);
</script>
</body>
</html>
The "classical" implementation of inheritance illustrated above has several disadvantages:
- The definition of an object's inheritance spreads over several declarations;
- The result of the inheritance depends on the order that the inheritance is declared. Which code is executed first depends on JavaScript's operation of assignment;
- Classical inheritance requires creating object instances for binding objects;
- References to JavaScript modules (script statements) in an HTML document must be properly organized. A small number of scripts will cause few serious issues, but large numbers of included scripts pose complex problems such as the order of script embedding, and resolving cross-references and dependencies among the embedded scripts.
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 inheritance for prototypal 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 snippet illustrates 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>The 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' Lazy Inheritance
JSINER (originated from JavaScript INheritance supportER) represents a JavaScript library intended to simplify writing object oriented code in JavaScript as well as managing external scripts dependencies.
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:
- "Lazy inheritance" has the same benefits as classical JavaScript prototype features;
- It is not necessary to maintain proper order of script files declaration (contrary to "classical inheritance");
- 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);
- 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";
- 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 "Lazy inheritance" 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 corresponding by "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
*/
Person.prototype.toString = function()
{
return "Person: " + this.getName();
};
Now 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>The JavaScript Lazy 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 f irst 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;
}
/**
* 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
*/
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>The JavaScript Lazy 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
</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 is 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’ Inheritance 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:
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.
Where it works
Proposed JSINER' Lazy inheritance approach was tested under
- Windows Internet Explorer 6.0/7.0
- Mozilla Firefox 1.5/2.0
- Opera 9.10
- MAC Safari 2.03
- MAC Firefox 1.5.0.11
- MAC Opera 9.0
Time your website with
WebWait - from the creator of AjaxPatterns.org
