Friday, November 4, 2011

7 ways to create objects in Javascript

Javascript is one of the few object oriented languages without classes. Nevertheless it is possible to create new objects choosing among several different techniques some of which may seem confusing to the newbies.
In this article I will go through seven different ways to create objects in Javascript, pointing out the pros and cons in each of them.
Before starting, it is worth remembering that a Javascript object is a simple collection of properties. Each property is a key-value pair. The value can be a primitive type, an object, or a function. Properties can be added, updated or removed at any time.

1. Object constructor

The simplest way to create an object is to use the Object constructor:
var person = new Object();
person.name = "Diego";
person.getName = function(){
    return this.name;
};
The main advantage of this approach is its simplicity.
On the other hand, instantiating multiple objects of the same type leads to a lot of code duplication. Moreover, it is not a compact construct which means it is not intuitively straightforward in pointing out to where the object's construction ends.

2. Literal notation

var person = {
    person.name : "Diego",
    person.getName : function(){
        return this.name;
    }
}
While the literal notation is more compact and elegant than the Object constructor, it is still not reusable.

3. Factory function

The Factory function allows to encapsulate and re-use the logic for creating similar objects. It leverages any of the previous constructs for this.
Either:
var newPerson=function(name){
    var result = new Object();
    result.name = name;
    result.getName = function(){
        return this.name;
    };
    return result;
};
var personOne = newPerson("Diego");
var personTwo = newPerson("Gangelo");
console.log(personOne.getName()); // prints Diego
console.log(personTwo.getName()); // prints Gangelo
Or:
var newPerson=function(name){
    return {
        person.name : name,
        person.getName : function(){
            return this.name;
        };
};
var personOne = newPerson("Diego");
var personTwo = newPerson("Gangelo");
console.log(personOne.getName()); // prints Diego
console.log(personTwo.getName()); // prints Gangelo
Code re-use and encapsulation are the main advantages of this pattern.
Now suppose you have an object X at a certain point in your code. How do you determine if X is a person or not? You could use some ad-hoc technique but the most elegant approach would be to interrogate the instanceOf operator.

4. Function Constructor

In Javascript it is possible to call any function with the new operator in front of it.
Given a function F, for new F():
  • a new empty object X is created.
  • X is set as context for F meaning throughout F this points to X.
  • X is returned as result of F
function Person(name){
        this.name = name;
        this.getName = function(){
            return this.name;
        };
};
var personOne = new Person("Diego");
console.log(personOne.getName()); // prints Diego
console.log(personOne instanceOf Person); // prints true
console.log(personOne.constructor === Person); // prints true
console.log(personOne instanceOf Object); // prints true
This time instanceof behaves the way we desired.

The constructor pattern is compact, reusable and gives a classical OO syntax.
Still, it is not perfect! In our example the field getName is a function. In Javascript functions are objects. Same function defined ten times means ten different objects created. Escalate this to a thousand and realize how much memory is being wasted!
It would be nice if all instances of Person share the same getName object, since this holds behavior and not data.

5. Prototype

Functions are very special in Javascript. They are objects, they can create other objects and they automatically get a field called prototype.
A prototype is a plain object with a single field, called constructor, pointing to the function itself.
What makes it special is that every object created through a function inherits the function's prototype.
function Person(){};
Person.prototype.name = "Diego";
var personOne = new Person();
var personTwo = new Person();
console.log(personOne.constructor == Person); // prints true
console.log(personOne.name); // prints Diego
console.log(personTwo.constructor == Person); // prints true
console.log(personTwo.name); // prints Diego
Let's see more in detail what happens:
  • Line 1: Person function is created. It automatically gets the prototype field.
  • Line 2: A field called name is added to Person's prototype.
  • Lines 3 and 4: Two instances of Person are created. They both get a field called __proto__ pointing to Person's prototype.
  • The last four lines show how the two objects share all properties present in Person's prototype
Why is the name field accessible directly from the object and not through the __proto__ field?
In order to answer this, we first have to understand how property lookup works in Javascript.
Suppose you want to access a field called name in an object foo:
  • if foo has a property name, the relative value is returned.
  • if foo does not have a property name but __proto__ does, __proto__'s value is returned.
  • if neither foo nor __proto__ have the property, then __proto__'s prototype is checked. And so on...
Due to time and space constraints, I will not talk more about prototypes except emphasizing that not all Javascript implementations provide access to __proto__. Therefore, it is better if you never try to access it.

The prototype pattern solves the issues encountered within the function constructor.
On the other hand, the opposite problem arises: All instances share the same fields. This is good for functions, but not for the rest!
personOne.name = "Filippo";
console.log(personOne.name); // prints Filippo
console.log(personTwo.name); // prints Filippo

6. Function/Prototype combination

The function/prototype combination, as you would imagine, takes advantage of both approaches :)
function Person(name){
        this.name = name;
};
Person.prototype.getName = function(){
            return this.name;
        };
var personOne = new Person("Diego");
var personTwo = new Person("Filippo");
console.log(personOne.getName()); // prints Diego
console.log(personTwo.getName()); // prints Filippo
console.log(personOne.getName === personTwo.getName) //prints true
While sharing behavior (the getName function), each object has its own data and state.

7. Singleton

Sometimes, you may want to make sure that only a single instance of a certain class exists.
To get a Singleton in Javascript is as simple as defining and invoking the constructor at the same time:
var singleton = new function(){
    this.name = "ApplicationName";
};

Conclusions

In this article we have seen the different techniques available for creating objects in Javascript.
Things will change a lot when Harmony will be on the road. Until that day however, we have to keep using these techniques, which are not that bad after all :)

10 comments:

  1. nice overview .. thanks for putting it together!

    ReplyDelete
  2. yep. thanks a bunch

    ReplyDelete
  3. Good one...
    I would like to learn the usage and advantages of underscore.js over other js frameworks, can you share that if you have any idea

    ReplyDelete
  4. Gud 1.. Little more explanation would have made it to next level

    ReplyDelete
  5. What about
    var obj = Object();
    var obj = Object.create(null);
    var obj = ({});

    Javascript is just driving me mad...

    ReplyDelete
  6. #2 won't work. You can't set property names using the literal constructor to be properties on the object you're constructing. MDN: " each property is an identifier (either a name, a number, or a string literal)". The dot notation is a way to access the property on an object. You could do it this way:
    var person = {};
    person.name = "Diego"
    person.getName = function(){...};

    ReplyDelete