Welcome to our shiny, new JAXenter: Seen a bug? Do tell us.
Time to embrace some new features

A modern approach to object creation in JavaScript

MarcoEmrich
peacock

Start using Douglas Crockford’s Object.create method, now supported by all modern browsers.

After stalling for several years, we’ve recently seen JavaScript language development making a fair amount of progress. The ECMAScript5-standard is now almost 3 years old and all major browser vendors support it in their current versions. So this may be the right time to really embrace new language features and adopt a modern style of development.

If you use JS for more than just jQuery-one-liners, you will need a structured approach. There are many ways to structure your code in JS. One great and also very common way to do it is to use objects and prototypical inheritance.

The old way

Let’s assume you build a shop and you need to express products. In the old days – before ECMAScript5 (ES5), you would have written something like this:

var Product = function(name) {
    this.name = name;
};
Product.prototype.showName = function() {alert(this.name);};

var myProduct = new Product("Super Fancy TV");
myProduct.showName();

Actually, this isn’t bad. You have a constructor to create your objects and attach methods to the prototype. All individual products have these methods, but there are several weak points to consider:

  • There are no private attributes in the JavaScript language. The value of name can be changed any time from the outside.
  • The methods are scattered. Even if you put them all in one place, there is no single structure to define your object concepts – like a class in many other object oriented languages.
  • You can easily forget to use the “new” keyword. This won’t throw an error, it just leads to completely different behaviour and probably to some really nasty bugs that are hard to find.
  • Inheritance will provide even more problems. There isn’t an agreed way in the JS-community of how to do it properly.

Because of these points, many developers have created libraries, frameworks and tools that provide all types of object creation and instantiation logic. Many of them introduced classes (e.g. Prototype or Coffescript).

You can take this path if you want, but I wouldn’t recommend it. It may be hard to believe, but prototypical inheritance is actually much easier than class-based – and even provides additional benefits. Just take a look at other prototypical languages like IO or Self. It is the old JavaScript syntax that made prototypes hard to use.

A better approach was pioneered by Douglas Crockford. He wrote a short Object.create method that has made its way into the ES5 standard – in a slightly extended form.

Object.create

The good news is that you don’t need a modern browser with an ES5 implementation. There is a polyfill from the Mozilla Developer Network. It allows you to use the new way of creating objects, even in older browsers like IE8 and below. This is important, considering that IE7/IE8 still has a combined market share of about 9%. It is not an issue if you only target Firefox, Chrome, Safari or Opera users. If you are in doubt, take a look at the compatibility chart.

if (!Object.create) {
    Object.create = function (o) {
        if (arguments.length > 1) {
            throw new Error('Object.create implementation only accepts the first parameter.');
        }
        function F() {}
        F.prototype = o;
        return new F();
    };
}

Just include the polyfill from above you will have an Object.create function. The first line of the snippet checks if Object.create is already there. This ensures that the polyfill won’t override the native implementation if the current browser provides one. If you need additional polyfills for other ES5-features, you can use Kris Kowal’s es5-shim.

Solo Objects

Let’s take another look at the shop project. If you would like to create only one product, there is no need for Object.create. Just create the object directly.

var myProduct = {
      price: 399.50,
      name: 'Super Fancy TV'
};

However, this isn’t the best option. You can manipulate the properties of the objects easily from the outside and there is no way to check or transform the assigned values. Take in to consideration an assignment like MyProduct.price = -20.

A mistake like this one would ensure that your company sells a lot of products fast and has really happy customers – customers that receive 20 bucks with every purchase. Your company wouldn’t be able to do that for long!

Years of experience in object oriented programming and design have told us to separate the inner state of an object from its outer interface. You usually want to make attributes private and provide some getter- and setter-methods.

Getters and Setters

ES5 provides a great concept for accessing properties with getters and setters. The sad news is that there is no way to get them to run in older browsers – you can’t just use a polyfill. So for the next two years or so, most of us don’t have the luxury to use it. In the meantime, there are many approaches you could use (see JavaScript Patterns by Stoyan Stefanov). All of them have their own distinct set of advantages and disadvantages. There is no single solution, so it comes down to a matter of taste. Here is what I usually do:

  • Prefix attributes with an underscore, e.g. _price. This is just a convention to mark the attribute as private. You should never call them from outside the object; violations to this rule are usually easy to spot. There are also more sophisticated approaches using closure-based patterns to archive real privacy (e.g. in Stefanov’s book). My opinion is that they aren’t worth the effort, most of the time.
  • Provide a setter method that starts with “set”, e.g. setPrice(value) . There is no way in older browsers to overwrite the assignment operator =. So this is the next best thing. Java and C++ programmers are used to it.
  • Provide a getter method with the name of the original attribute, e.g. price(). Many programmers prefer prefixing the method with “get”. I think this isn’t necessary in JS and just makes the code less readable – your mileage may vary.
  • Sometimes an attribute may be for internal use only – or you want it to be ready-only. In this case, just leave out the appropriate methods. So a better implementation may be this.

    var myProduct = { _price: 99.50, _name: ‘Grindle 3′,

    price:    function()  {return this._price;},
    name:     function()  {return this._name;},
    setPrice: function(p) {this._price = p;},
    

    };

If you want to check the price before setting it, you can now do this easily.

setPrice: function(p) {
    if (p <= 0) {
        throw new Error("Price must be positive");
    }
    this._price = p;
}

The real ECMAScript5 implementation is even nicer. It has the added benefit of calling the getters and setters implicitly, e.g. myProduct.price = 85.99. Finally, JavaScript supports the uniform access principle!

var myProduct = {
...
        get price() {return this._price;},
        set price(p) {
            if (p <= 0) { throw new Error("Price must be positive"); }
            this._price = p;
        },

...

Prototypes

A real shop will, of course, have many products. And you don’t want to create them all from scratch. You will need a place, where you can put common structure and behaviour. The usual pattern in JS is to create a parent object for this – a prototype of a product, where all other products are derived from. With Object.create you build new products from it.

var Product = {
    _price: 0,
    _name: '',

    price:    function()  {return this._price;},
    name:     function()  {return this._name;},
    setPrice: function(p) {this._price = p;},
    setName:  function(n) {this._name  = n;}
};

var product1 = Object.create(Product);
product1.setName('Grindle 3');
product1.setPrice(99.50);

Initializes

It may be a bit inconvenient to call all necessary setters for all new instances of Product. To circumvent that, provide an initialize-Method, similar to a constructor from other languages.

var Product = {
    ...
    init: function(name, price) {
        this._name = name;
        this._price = price;
    },
    ...
};
var product1 = Object.create(Product).init('Grindle 3', 99.50);

Inheritance

A great advantage of the prototypical approach is the unification of instantiation and inheritance. You don’t need anything special – just use Object.create for inheritance as well.

var Product = {
...
};

var Book = Object.create(Product);
Book._author = null;
Book._numPages = null;
Book.setAuthor   = function(author) {this._author = author;};
Book.setNumPages = function(num_pages) {this._numPages = num_pages;};
Book.author      = function() {return this.author();};
Book.numPages    = function() {return this.numPages();};

Book is a new object that is derived from Product. Instead of setting specific values, like name and price, it gets additional structure and behaviour. The new attributes author and numPages make up the structure, while their simple getters and setters serve as placeholder examples for some real behaviour.

The downside is that calling Book several times is redundant and the whole syntax is quite different from defining the base object. Therefore, you usually build a small extend function that makes inheritance a little more convenient.

Object.prototype.extend = function(newProperties) {
    for (var propertyName in newProperties) {
        this[propertyName] = newProperties[propertyName];
    }
    return this;
};

Again, you wouldn’t need this in real ES5. The real Object.create allows for a second argument containing the extensions.

You can now factor your code using the new extend method.

var Product = {
...
};

var Book = Object.create(Product).extend({
    _author: null,
    _numPages: null,
    setAuthor:   function(author) {this._author = author;},
    setNumPages: function(num_pages) {this._numPages = num_pages;},
    author:      function() {return this.author();},
    numPages:    function() {return this.numPages();}
});

Inside Prototypical Inheritance

The objects- and prototype-concept has many advantages over static class-based approaches, like:

  • Unification of inheritance and instantiation. You can use the same mechanism (Object.create) to inherit from a prototype or build instances from it (similar to class usage). Actually – it is the same thing.
  • Inheritance of values. You can inherit values from prototypes; there is no need to set defaults in a constructor.
  • Runtime modification of prototypes. There is no difference between run-time and compile-time in JS. You can modify prototypes during program execution while more static languages only allow changes to classes before the compiler runs. This isn’t a direct advantage of prototypical inheritance; it’s the dynamic nature of JS. There are even class-based languages available, which allow for runtime modification of classes – Ruby or Smalltalk would fit the bill. But JavaScript’s objects-only-approach makes this much simpler – a real gain if you like to do any metaprogramming.

Conclusion

Object.create provides a much more attractive way to use prototypes, but there are still some quirks left. You’ll have to implement your own getters/setters, init- and extend-Methods to get it working on older browsers. These disadvantages will eventually fade away when you drop the shim and thrive on real ECMAScript5 – which you can already do if IE8 and below are no concern for you. Until then you can start do adopt the new approach using Object.create. It may become the standard approach sooner than you think.

Marco Emrich started professional software development in 1993 and has used many different languages and technologies. He holds an academic degree in computer science (German Dipl. Inform.) and did some research for the Fraunhofer IESE Institute in the area of generative programming. Currently, he is employed by “webmasters akademie”, a German IT training centre. There, he works as author, trainer, software developer and project manager. He also likes coding for fun, like for his spare time project fantasy-cards.net.

This article was originally published in Web & PHP Magazine. Photo by Madison Berndt.

Author
Comments
comments powered by Disqus