JavaScript: Prototype Pattern

I’m often working with some large javascript files in my day job. A lot of this is ‘spaghetti code’, it can be quite messy to navigate around and maintain. So I have been looking at ways of writing more maintainable code, which led me to look at JavaScript design patterns. Today I was looking at the Prototype pattern. I hope to follow up to this post with more about the reasons to apply design patterns. Just now I’ll give some basic examples of the prototype pattern.

To start with, we create a constructor which will contain the instance variables. Each object will have a separate copy of the variables (in this case ‘display’).

            var Calculator = function(eq){
                this.display = document.getElementById(displayDiv);
            };

Now we can add functions to objects of ‘Calculator’. These functions are loaded into memory once, regardless of how many objects are created. Functions are name-value pairs within an object literal. ‘this.display’ refers to the ‘display’ variable belonging to this instance of Calculator. It is assigned the value of x + y.

            Calculator.prototype = {
                add:function(x, y){
                    var value = x + y;
                    this.display.innerHTML = value;
                }
            }

To call this function, we would first create an instance of Calculator (supplying a div which will be used to display the result of the calculation), and then we call the ‘add’ function.

            var myCalc = new Calculator('display');
            myCalc.add(2, 2);

Instead of going into the source code to change a function, it is possible to over-ride the existing functions of Calculator. In this case we will change the ‘add’ function slightly.

            Calculator.prototype.add = function(){
                var value = x + y + 1;
                this.display.innerHTML = value;
            }

As it stands, ‘Calculator’ is in the global scope. If we were using some other code in the same project which also used ‘Calculator’ objects as we have done, our code might not run as intended. We can prevent these types of conflict by adding our own namespace such as a company name.

            var acme = acme || {};
            acme.Calculator = function(eq){
                this.display = document.getElementById(displayDiv);
            };
            
            acme.Calculator.prototype = {
                add:function(x, y){
                    var value = x + y;
                    this.display.innerHTML = value;
                }
            }

And we would instantiate the object slightly differently…

            var myCalc = new acme.Calculator('display');
            myCalc.add(2, 2);

Some of the key points of the prototype pattern are:

  • Efficient – You can create many object instances, but the functions are only loaded into memory once
  • All variables are kept in the constructor, and are unique to each instance
  • Methods/functions can be over-ridden – source code doesn’t need to be edited. Overrides can be in a separate JS file
  • Namespaces, although not part of the Prototype Pattern, can be used to take objects out of the global scope
  • Prototype pattern can help structure JS, add encapsulation and simplify maintentance/extention