Yan Cui
I help clients go faster for less using serverless technologies.
This article is brought to you by
I never fully recovered my workspace setup when I upgraded my laptop two years ago, and I still miss things today. If only I had known about Gitpod back then…
The other day I put up a post with some quick bullet points about objects in javascript to help someone from a static, strongly typed language (like C# or Java) background understand javascript’s dynamic and loose type system. One important thing I haven’t talked about yet is the way inheritance work in javascript, which again, is very different from those found in C# or Java.
Most object oriented languages like C# and Java use classes to define types, which are basically blueprints for creating objects, this approach to inheritance is referred to as classical inheritance. Javascript on the other hand, has no classes, instead objects inherit from other objects, this approach is referred to as prototypal inheritance.
Constructor functions
You can use functions, which themselves are objects to act as constructors and provide blueprints for creating objects:
function Person(name, age) { // properties this.Name = name; this.Age = age; // methods this.sayHello = function () { return "Hello, my name is " + this.Name + "!"; } }
This creates a new object Person.prototype, which all new Person objects will inherit from. You can then use the new operator to create a new Person object using this constructor:
var person = new Person("John", 10);
The person object inherits all the members (properties and functions) that are defined by the constructor function above, as can be seen in Chrome’s debugger (CTRL+SHIFT+J):
Dynamically modifying the prototype
It is possible to make changes to Person.prototype object outside of its constructor function, to add a sayBye function to the Person.prototype object:
Person.prototype.sayBye = function (recipient) { return "Good bye, " + recipient + "!"; }
Once defined, this new function will be available on all Person objects, even if they were created prior to the new function:
var goodbyeMsg = person.sayBye("Yan"); // returns "Good bye, Yan!"
Understanding how members are looked up
This is all the code we have written so far:
function Person(name, age) { // properties this.Name = name; this.Age = age; // methods this.sayHello = function () { return "Hello, my name is " + this.Name + "!"; } } var person = new Person("John", 10); Person.prototype.sayBye = function (recipient) { return "Good bye, " + recipient + "!"; }; var goodbyeMsg = person.sayBye("Yan"); // returns "Good bye, Yan!"
If you run the code to the end, you will see the sayBye function we added at the end of the example:
It falls under the person object’s __proto__ member rather than on the object itself (as is the case for the constructor defined members – Name, Age and the sayHello function) but it’ll still be accessible directly on the person object.
When accessing a member on an object, Javascript will check in that object first, if the member is not found it will then follows the inheritance chain until either the member is found or it will return undefined if it has exhausted all the objects on the inheritance chain.
In order for javascript to keep track of this chain and figure out where next to look for a member, an internal property __proto__ is used to point to the next object in the chain. Because every object is ultimately inherited from Object, it’ll be at the top of every object’s inheritance chain and the last object you’ll be able to fetch if you continuously invoke the __proto__ member before you get undefined returned:
myObj.__proto__.__proto__….
Extending objects
Normally to extend an object you need to perform a two step process:
1. Invoke the base constructor
function Employee(name, age, salary) { // equivalent to calling base(name, age) in C# // this invokes the base class's constructor on the current object Person.call(this, name, age); // new property and method this.Salary = salary; this.reportSalary = function () { return "Hi, my name is " + this.Name + ", my salary is " + this.Salary + "."; } }
As you can see, this invokes the Person constructor function (see above) on the current object, if you try to create a new object using this constructor:
var employee = new Employee("Jane", 20, 12000);
this is what the object looks like:
Notice anything missing? What happened to the sayBye function we added to the Person object outside of its constructor?
Well, because the sayBye function is not defined in the Person object’s constructor function and all we’ve done so far is to invoke that function to set up some properties and functions for a new Employee object so of course it won’t include anything that’s dynamically added to the object outside of its constructor function.
2. Set up the inheritance relationship
This second step is simple but crucial, this is how you set up the ‘relationship’ between the Person and Employee objects:
// make Employee inherit from Person Employee.prototype = new Person();
Doing this tells Javascript to use Person as the prototype for Employee, i.e. prototypal inheritance’s way of saying using Person as superclass to Employee. Now that you’ve done this step, if you try to create a new Employee object now this is what your object looks like:
var employee = new Employee("Jane", 20, 12000);
Checking an object’s inheritance chain
You can check if an object appear in another object’s inheritance chain using the isPrototypeOf function. For example, using the Person and Employee constructor functions (remember, they are objects) defined above:
var person = new Person("John", 10); var employee = new Employee("Jane", 20, 10000); Employee.prototype.isPrototypeOf(person); // false Person.prototype.isPrototypeOf(person); // true Object.prototype.isPrototypeOf(person); // true Employee.prototype.isPrototypeOf(employee); // true Person.prototype.isPrototypeOf(employee); // true Object.prototype.isPrototypeOf(employee); // true
Where the inheritance technique fails
Whilst the method of extending an object above works it does have an implicit requirement for the parent object’s constructor function to allow a parameterless constructor to work, which can be a problem in certain situations.
For instance, if the Person object’s constructor explicitly requires the parameters name and age to be specified:
function Person(name, age) { if (!name || !age) { throw new Error("A person must have a name and age"); } // properties this.Name = name; this.Age = age; // methods this.sayHello = function () { return "Hello, my name is " + this.Name + "!"; } }
In situations like this, this line will except:
Employee.prototype = new Person(); // Uncaught Error: A person must have a name and age
Other inheritance techniques
Besides the technique I mentioned above, here are two other ways to create object inheritance for your reference.
Douglas Crockford’s initialize technique
In Douglas Crockford’s post here he wrote about a different technique on initializing objects, in his last update in 2008 this is his preferred method:
if (typeof Object.create !== 'function') { Object.create = function (o) { function F() { } F.prototype = o; return new F(); }; }
and to use it, suppose we have already created an Employee object called employee, to create another just like it:
var employee2 = Object.create(employee);
This technique is more about cloning an existing object, employee2 will therefore inherit all of employee‘s current state:
MooTools
MooTools is a modular Object-Oriented Javascript framework, it includes a Class constructor function which includes some nice features such as:
- Extends – allows you to specify a base class which to extend from
- Implements – allows you to specify one or more classes to adopt properties from (think implementing an interface)
So using MooTools, this is how you can create the Person and Employee classes:
// create a new Person class var Person = new Class({ initialize: function (name, age) { // properties this.Name = name; this.Age = age; // methods this.sayHello = function () { return "Hello, my name is " + this.Name + "!"; } } }); // add the sayBye function to the Person class Person.implement({ sayBye: function (recipient) { return "Good bye, " + recipient + "!"; } }); // create a new Employee class which inherits from the Person class var Employee = new Class({ Extends: Person, initialize: function (name, age, salary) { this.parent(name, age); // calls initialize method of Person class this.Salary = salary; // new property and method this.Salary = salary; this.reportSalary = function () { return "Hi, my name is " + this.Name + ", my salary is " + this.Salary + "."; } } }); var person = new Person("John", 10); var employee = new Employee("Jane", 20, 12000);
This is how the person and employee objects look in the Chrome Debugger:
References:
Douglas Crockford on Prototypal Inheritance
Whenever you’re ready, here are 3 ways I can help you:
- Production-Ready Serverless: Join 20+ AWS Heroes & Community Builders and 1000+ other students in levelling up your serverless game. This is your one-stop shop for quickly levelling up your serverless skills.
- I help clients launch product ideas, improve their development processes and upskill their teams. If you’d like to work together, then let’s get in touch.
- Join my community on Discord, ask questions, and join the discussion on all things AWS and Serverless.