I wanted to write my own post about Object-oriented JavaScript for quite some time now. Like many others, i had a hard time deciphering some of its behaviors and it is true that Object-oriented JavaScript can be rather confusing even for experienced programmers, especially those who are used to the Inheritance models of C#, Java and similar languages. There are plenty of blogs on the web, but as is usually the case with complex topics, you need to go through multiple resources to get all the answers. Here i ll try to sum up everything i know (or think i know) about the subject. If any of you out there get some benefit out of this as well, then great! Let’s get started!
Some basic definitions coming from a simplification (where possible) of the ECMAScript spec is a good place to start.
Some Basics
JavaScript is object-based. A JavaScript program is a collection of communicating objects. Remember that in JavaScript even functions are objects. An object is a collection of properties each with zero or more attributes that determine how each property can be used. Properties are containers that hold other objects, primitive values or functions. JavaScript defines a collection of built-in objects such as the global object, the Object object, the Function object and more.
JavaScript does not use classes and class inheritance. Instead an object in JavaScript has an internal property called its prototype that points to a second object from which the first object inherits properties. Multiple objects can be linked like that resulting in a prototype chain that ends when the prototype property at the “root” of the chain is null. For example let’s assume we have 2 objects called Person and Student.
The student object inherits from person by having its internal prototype property pointing to it, person inherits from Object.prototype. Object.prototype has its own internal prototype property that is always null. That is the end of our chain and the end of all chains in JavaScript. (There is just one rather insignificant exception to this rule, you can create an object and have its prototype set to null which means it does not inherit anything at all but i don’t see much use for this)
Object.prototype
One of the things that can be confusing is what exactly is at the top of a chain in JavaScript. It is easy to assume that it is Object and then get confused about the relationship between Object & Object.prototype. The reality is that at the top you have Object.prototype which is a standard built-in object itself and which contains all the methods that you would normally inherit. So forget about Object and remember that what you are inheriting from is Object.prototype. The inherited methods are hasOwnProperty(), isPrototypeOf(), propertyIsEnumerable(), toSource(), toLocaleString(), toString().
To prove the point we can use the Object.getPrototypeOf() method
var o = { x: 1}; Object.getPrototypeOf(o) === Object.prototype //=> true console.log(o.hasOwnProperty('x')); // prints true
There is a very simple way to set an object’s prototype and that is using the __proto__ property. The problem is that this property is deprecated and is not supported by all browsers so it is not for use in production code but it definitely has an educational value and it can come handy when debugging as well. As an example of how the prototype chain works for a student and person object.
var person = { name: "John", age: 22, displayName: function() { console.log(this.name); } }; var student = { Id: 123, displayId: function() { console.log(this.Id); } }; student.__proto__ = person; student.displayName(); student.displayId();
__proto__ vs prototype
It may be a basic mistake but it is common to get confused between student.__proto__ and student.prototype. __proto__ is the actual link in your prototype chain but as explained it is not meant to be used for production code. It is an internal property of the object which is not meant to be accessible although exposed with __proto__ in some implementations. You cannot use student.prototype in the above code. The prototype property can only be used with constructors as explained later.
3 ways of creating objects
Modern JavaScript supports 3 ways of creating objects: object literals, the Object.create() method and constructors.
Object Literals
An object literal is a comma separated list of name:value pairs. Objects created using objects literals have their internal prototype property pointing to Object.prototype
var point = {x:1, y:2}; // point ----> Object.prototype ----> NULL
Object.create()
New to the ECMAScript 5 standard is the Object.create() method which creates a new object and uses its first argument as the prototype of the newly created object.
var person = {name:"john"}; var student = Object.create(person); student.Id = 123; console.log(student.Id); // prints 123 console.log(student.name); // prints john console.log(student.__proto__ === person); // prints true console.log(Object.getPrototypeOf(student) === person); // prints true // student ----> person ----> Object.prototype ----> NULL
You can use Object.create to create an object that does not inherit anything or simply a normal Object that inherits from Object.prototype
var o1 = Object.create(null); // o1 ----> NULL var o2 = Object.create(Object.prototype); // o2 ----> Object.prototype----> NULL
Constructors
Again, it would be hugely beneficial if we refer to the ECMAScript specification for this.
Objects may be created in various ways including via a literal notation or via constructors which create objects and then execute code that initializes all or part of them by assigning initial values to their properties. Each constructor is a function that has a property named “prototype” that is used to implement prototype-based inheritance and shared properties. Objects are created by using constructors in new expressions; for example, new Date(2009,11) creates a new Date object. Invoking a constructor without using new has consequences that depend on the constructor. For example, Date() produces a string representation of the current date and time rather than an object.
Every object created by a constructor has an implicit reference (called the object‘s prototype) to the value of its constructor‘s “prototype” property. Furthermore, a prototype may have a non-null implicit reference to its prototype, and so on; this is called the prototype chain. When a reference is made to a property in an object, that reference is to the property of that name in the first object in the prototype chain that contains a property of that name. In other words, first the object mentioned directly is examined for such a property; if that object contains the named property, that is the property to which the reference refers; if that object does not contain the named property, the prototype for that object is examined next; and so on.
So constructors are nothing more than functions whose purpose is the initialization of a new object. The object creation itself is the result of calling this function with the new keyword. The prototype property of the constructor is actually what objects created from this constructor inherit. To clarify this point.
function Person(name, age) { this.name = name; this.age = age; } Person.prototype = { displayPersonInfo: function(){ console.log("Name is " + this.name + " and age is " + this.age); } } var p = new Person("Peter", 24); p.displayPersonInfo(); function Student(id, name, age) { this.id = id; this.base = Person; this.base(name, age); } var p = new Person(); Student.prototype = p; // done like this for demonstration purposes // Student.prototype = new Person(); is the norm var s1 = new Student(123, "George", 32); s1.displayPersonInfo(); // prints "Name is George and age is 32" var s2 = new Student(234, "Maria", 22); s2.displayPersonInfo(); // prints "Name is Maria and age is 22" console.log(Object.getPrototypeOf(s1) === p); console.log(Object.getPrototypeOf(Object.getPrototypeOf(s1)) === Person.prototype) console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(s1))) === Object.prototype) console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(s1)))) === null)
You can see how we use Person’s prototype property to set it to an object with methods (in this case the single method displayPersonInfo) we want inherited from all objects constructed with Person. We also have a Student constructor and we set its prototype property to be Person. We also use the Person constructor within the Student constructor to initialize a student object properly.
So the prototype chain for the above example as can be verified with the console.log statements at the end is
s (Student) ----> p (Person) ----> Person.prototype ----> Object.prototype ----> NULL
As is usually the case in JavaScript there are many different ways to write a piece of code. We could change and perhaps improve (that can be quite subjective) a few things. Below we will use the Call() function to call the Person constructor and also add the method displayPersonInfo directly to the prototype without using the object literals syntax.
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.displayPersonInfo = function(){ console.log("Name is " + this.name + " and age is " + this.age); } var p = new Person("Peter", 24); p.displayPersonInfo(); function Student(id, name, age) { this.id = id; Person.call(this, name, age); } Student.prototype = new Person(); var s1 = new Student(123, "George", 32); s1.displayPersonInfo(); // prints "Name is George and age is 32" var s2 = new Student(234, "Maria", 22); s2.displayPersonInfo(); // prints "Name is Maria and age is 22"
For those of you unfamiliar with Call(), Call() allows you to indirectly call a function as if it was part of some different object. The first argument to Call() is the object on which the function is to be invoked. In our example we call Person passing this – the Student – as our first argument. This technique can be used for achieving calls to base methods in JavaScript.
It ‘s important to understand exactly what’s happening in the above example and how the pieces fall into place.
Prototype of Constructors
Any JavaScript function can be used as a constructor and for that reason a prototype property is automatically created for every function, to provide for the possibility that the function will be used as a constructor. The value of this property is an object that has a single “constructor” property the value of which is the function object, so the constructor simply points back to the function.
var f = function() {}; f.prototype.constructor === f; //=> true
When we had these lines in the code.
Person.prototype.displayPersonInfo = function(){ console.log("Name is " + this.name + " and age is " + this.age); }
we effectively added the displayPersonInfo() method to the object pointed to by the prototype property of Person.
When we did
Student.prototype = new Person();
we simply set the object the Student constructor prototype property points to, to a new Person object and since a Person object always inherits from Person.prototype we managed to share the displayPersonInfo() method between the different Student & Person objects created. That may be better explained with a diagram.
Remember the basic rule? Every object created by a constructor has an implicit reference (called the object‘s prototype) to the value of its constructor‘s “prototype” property.
“Fixing” the constructor
In case you prefer setting a prototype property using the object literals syntax as in
Person.prototype = { displayPersonInfo: function(){ console.log("Name is " + this.name + " and age is " + this.age); } }
then there is a small fix you need to make. As we said before, the default object pointed to by the prototype property of a constructor contains a single, constructor property pointing back to the constructor. If you wish to preserve this functionality you need to add the constructor property yourself as in
Person.prototype = { constructor: Person, displayPersonInfo: function(){ console.log("Name is " + this.name + " and age is " + this.age); }
To avoid this issue all together, you can simply add properties to the prototype object.
Person.prototype.displayPersonInfo = function(){ console.log("Name is " + this.name + " and age is " + this.age); }
“Static” and “Instance” members
You can emulate static and instance like members in JavaScript. Typically,
- instance fields are defined and initialized within the constructor.
- instance methods to be inherited by all objects are added in the prototype object.
- static methods or fields are defined on the constructor object itself.
As an example
function Person(name, age) { //instance fields this.name = name; this.age = age; } //instance methods Person.prototype = { constructor: Person, displayPersonInfo: function(){ console.log("Name is " + this.name + " and age is " + this.age); }, checkAge: function(){ if (this.age <= Person.MAX_AGE_INSYSTEM) console.log("Age is OK."); } }; // "Static" field and method Person.MAX_AGE_INSYSTEM = 99; Person.displayMaxAge = function() { console.log(Person.MAX_AGE_INSYSTEM); } Person.displayMaxAge(); var p = new Person("Peter", 32); p.checkAge();
There is a lot more to OO JavaScript but hopefully the above covers all the fundamentals that one needs for effective OO programming with the language!