Revealing Prototype Pattern - Techniques, Strategies and Patterns for Structuring JavaScript Code
Using the JavaScript Revealing Prototype Pattern
This is the 4th post in a series on techniques, strategies and patterns for writing JavaScript code. In my previous post I discussed the Revealing Module Pattern – one of my favorite JavaScript patterns. If you like the features offered by the Revealing Module Pattern but want to take advantage of JavaScript’s prototype functionality and the benefits it offers, the Revealing Prototype Pattern may be what you need. It offers the benefits of the Revealing Module Pattern but also provides a way to share function implementations across object instances through prototyping. The pattern is a combination of the Prototype Pattern and Revealing Module Pattern.
As a quick review, I showed the following code in the first post. The code simply lists all functions directly with no encapsulation and defines several global variables. While the code works fine this way, I’ll examine how we can restructure it to follow the Revealing Prototype Pattern.
window.onload = function () { eqCtl = document.getElementById('eq'); currNumberCtl = document.getElementById('currNumber'); }; var eqCtl, currNumberCtl, operator, operatorSet = false, equalsPressed = false, lastNumber = null; function add(x,y) { return x + y; } function subtract(x, y) { return x - y; } function multiply(x, y) { return x * y; } function divide(x, y) { if (y == 0) { alert("Can't divide by 0"); return 0; } return x / y; } function setVal(val) { currNumberCtl.innerHTML = val; } function setEquation(val) { eqCtl.innerHTML = val; } function clearNumbers() { lastNumber = null; equalsPressed = operatorSet = false; setVal('0'); setEquation(''); } function setOperator(newOperator) { if (newOperator == '=') { equalsPressed = true; calculate(); setEquation(''); return; } //Handle case where = was pressed //followed by an operator (+, -, *, /) if (!equalsPressed) calculate(); equalsPressed = false; operator = newOperator; operatorSet = true; lastNumber = parseFloat(currNumberCtl.innerHTML); var eqText = (eqCtl.innerHTML == '') ? lastNumber + ' ' + operator + ' ' : eqCtl.innerHTML + ' ' + operator + ' '; setEquation(eqText); } function numberClick(e) { var button = (e.target) ? e.target : e.srcElement; if (operatorSet == true || currNumberCtl.innerHTML == '0') { setVal(''); operatorSet = false; } setVal(currNumberCtl.innerHTML + button.innerHTML); setEquation(eqCtl.innerHTML + button.innerHTML); } function calculate() { if (!operator || lastNumber == null) return; var currNumber = parseFloat(currNumberCtl.innerHTML), newVal = 0; //eval() would've made this a whole lot simpler //but didn't want to use it in favor of a more //"robust" set of methods to demo patterns switch (operator) { case '+': newVal = add(lastNumber, currNumber); break; case '-': newVal = subtract(lastNumber, currNumber); break; case '*': newVal = multiply(lastNumber, currNumber); break; case '/': newVal = divide(lastNumber, currNumber); break; } setVal(newVal); lastNumber = newVal; }
To start using the Revealing Prototype Pattern you’ll first create a constructor as with the Prototype Pattern and define any variables that are unique to an object instance:
var Calculator = function (cn, eq) { this.currNumberCtl = cn; this.eqCtl = eq; };
This example allows two DOM elements to be passed in which are stored in the currNumberCtl and eqCtl variables. Once the constructor is created you can define the prototype. Unlike the Prototype Pattern which assigns a JavaScript object literal to the prototype, the Revealing Prototype Pattern assigns a function which is immediately invoked as with the Revealing Module Pattern:
Calculator.prototype = function () { }();
The complete prototype definition for Calculator is defined using the following syntax (this code assumes that a single calculator will be created). Looking through the code you’ll notice that it’s quite similar to the Revealing Module Pattern but assigns the container function to the Calculator’s prototype rather than to a variable.
var Calculator = function (cn, eq) { this.currNumberCtl = cn; this.eqCtl = eq; }; Calculator.prototype = function () { var operator = null, operatorSet = false, equalsPressed = false, lastNumber = null, add = function (x, y) { return x + y; }, subtract = function (x, y) { return x - y; }, multiply = function (x, y) { return x * y; }, divide = function (x, y) { if (y == 0) { alert("Can't divide by 0"); } return x / y; }, setVal = function (val, thisObj) { thisObj.currNumberCtl.innerHTML = val; }, setEquation = function (val, thisObj) { thisObj.eqCtl.innerHTML = val; }, clearNumbers = function () { lastNumber = null; equalsPressed = operatorSet = false; setVal('0',this); setEquation('',this); }, setOperator = function (newOperator) { if (newOperator == '=') { equalsPressed = true; calculate(this); setEquation('',this); return; } //Handle case where = was pressed //followed by an operator (+, -, *, /) if (!equalsPressed) calculate(this); equalsPressed = false; operator = newOperator; operatorSet = true; lastNumber = parseFloat(this.currNumberCtl.innerHTML); var eqText = (this.eqCtl.innerHTML == '') ? lastNumber + ' ' + operator + ' ' : this.eqCtl.innerHTML + ' ' + operator + ' '; setEquation(eqText,this); }, numberClick = function (e) { var button = (e.target) ? e.target : e.srcElement; if (operatorSet == true || this.currNumberCtl.innerHTML == '0') { setVal('', this); operatorSet = false; } setVal(this.currNumberCtl.innerHTML + button.innerHTML, this); setEquation(this.eqCtl.innerHTML + button.innerHTML, this); }, calculate = function (thisObj) { if (!operator || lastNumber == null) return; var displayedNumber = parseFloat(thisObj.currNumberCtl.innerHTML), newVal = 0; //eval() would've made this a whole lot simpler //but didn't want to use it in favor of a more //"robust" set of methods to demo patterns switch (operator) { case '+': newVal = add(lastNumber, displayedNumber); break; case '-': newVal = subtract(lastNumber, displayedNumber); break; case '*': newVal = multiply(lastNumber, displayedNumber); break; case '/': newVal = divide(lastNumber, displayedNumber); break; } setVal(newVal, thisObj); lastNumber = newVal; }; return { numberClick: numberClick, setOperator: setOperator, clearNumbers: clearNumbers }; } ();
Looking through the code you’ll see that instead of assigning a JavaScript object literal to the prototype (as with the Prototype Pattern) a function is assigned instead. This makes the code a lot cleaner compared to the Prototype Pattern - in my opinion anyway. It also allows you take advantage of public/private visibility functionality through the return block so that only functions accessible to external objects are exposed.
There’s something interesting that happens with variables though especially if you plan on creating more than one Calculator object in a page. Looking at the public functions you’ll see that the “this” keyword is used to access the currNumberCtl and eqCtl variables defined in the constructor. This works great since the caller of the public functions will be the Calculator object instance which of course has the two variables defined. However, when one of the public functions calls a private function such as setVal(), the context of “this” changes and you’ll no longer have access to the two variables. There are a few tricks that can be used to deal with this, but to work around the context change I simply pass “this” from the public functions into the private functions. Although that technique works, it could certainly get messy if you have private functions calling a lot of other private functions. It’s definitely something to be aware of (if you happen to have a nice trick for handling that please leave a comment).
An instance of the Calculator can be invoked using the following code:
var calc; window.onload = function () { var cn = document.getElementById('currNumber'); var eq = document.getElementById('eq'); calc = new Calculator(cn, eq); };
It's important to note that this code assumes that a single Calculator object will be created. If multiple objects were created we'd have a problem since several variables such as operator, operatorSet, etc. are defined in the prototype function and would be shared across all instances. As you type in one calculator you'd see the values reflected in the other calculators which wouldn't be right. Check out the code download below for an example that moves all of the variables into the constructor in situations where multiple instances need to be created.
Also note that the "calc" variable above is a global variable which isn't ever recommended in a "real" app. Although in this case we can get away with it (it's just a simple demo), in a real app you'd likely want to prefix it with a namespace. Something like the following:
var myNS = myNS || {};
...
myNS.calc = new Calculator(cn, eq);
As with any discussion of patterns, this blog series only scratches the surface of what can be done. However, I hope it provides a good starting point for converting your JavaScript code from “function spaghetti code” into a more structured type of code.
Demos of all the patterns covered in this series can be downloaded below.
Download Code
Pluralsight Course - Structuring JavaScript Code in HTML5 Applications
If you're interested in additional information about structuring JavaScript code check out my Pluralsight course. Here's a sample from the course covering closures.
Demo - Working with Closures in JavaScript