Revealing Prototype Pattern: Structuring JavaScript Code – Part IV

- select the contributor at the end of the page -

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. It creates what I call “function spaghetti code”. 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. 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.

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);
};

Working with the JavaScript “this” Keyword

The Calculator code shown earlier works great since the caller of the public functions will be the Calculator object instance which of course has the two variables defined in the constructor. 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 defined in the constructor – without doing a little work anyway.

When JavaScript's “this” keyword is used with patterns such as the Prototype or Revealing Prototype patterns the code can be challenging in some cases. Unlike languages such as C# or Java, "this" can change context in JavaScript. For example, if a Calculator object named calc calls an add() function then "this" represents the Calculator object which means you can easily access any variables defined in the object such as a variable named tax by simply using this.tax.

//Using "this" inside of the add() function gets you to 
//the calc object
calc.add(2, 2);

However, if add() makes a call to another function then "this" changes context and no longer represents the Calculator object. In fact, "this" will change to the window object which means you can no longer access variables defined in the Calculator object such as tax. That presents a bit of a problem that's especially challenging if you've never dealt with it before.

There are several ways to handle this challenge. First, you can pass "this" as a parameter to other functions. An example of passing "this" between functions is shown next:

var Calculator = function (eq) {
//state goes here
this.eqCtl = document.getElementById(eq);
this.lastNumber;
this.equalsPressed;
this.operatorSet;
this.tax;
};

Calculator.prototype = function () {
//private members
var add = function (x, y) {
this.eqCtl.innerHTML = x + y + this.tax;
},

subtract = function (x, y) {
this.eqCtl.innerHTML = x – y + this.tax;
},

setVal = function (val, thisObj) {
thisObj.currNumberCtl.innerHTML = val;
},

setEquation = function (val, thisObj) {
thisObj.eqCtl.innerHTML = val;
},

//Other functions omitted for brevity
clearNumbers = function () {
this.lastNumber = null;
this.equalsPressed = this.operatorSet = false;

//Pass the Calculator object that called clearNumbers()
//to other functions as a parameter
setVal('0', this);
setEquation('', this);

};

//public members
return {
add: add,
subtract: subtract,
clearNumbers: clearNumbers
};
} ();

If a Calculator object calls a clearNumbers() function then you can easily access the Calculator object's constructor variables within the function. However, once clearNumbers() calls other functions such as setVal() or setEquation(), "this" changes context. To account for the change, the code passes "this" as a parameter to each of the functions and they then use it like normal. Although this type of code works, it pollutes your function parameters in some cases and becomes a little messy to work with (at least in my opinion).

Another technique that can be used involves JavaScript's call() function. This function can be used to invoke functions and set the context of "this" while the call is being made. For example, if you want to call a function named setVal() and preserve the current value of "this" as the call is made then you can do the following:

setVal.call(this, 'yourParameterValue');

The current context of "this" will be passed along automatically to the setVal() function and it can safely use this.tax in the case of a Calculator object.

The following code uses the call() function to update the code shown earlier. The clearNumbers() function uses JavaScript's call() function to invoke the setVal() and setEquation() functions and preserve the current context of "this" in the process. Notice that the setVal() and setEquation() functions no longer need the extra parameter as the functions shown earlier did and can simply use "this" to access Calculator object variables defined in the object's constructor. This simplifies the call by eliminating the need for the extra parameter and makes the code a lot cleaner.

var Calculator = function (eq) {
//state goes here
this.eqCtl = document.getElementById(eq);
this.lastNumber;
this.equalsPressed;
this.operatorSet;
this.tax;
};

Calculator.prototype = function () {
//private members
var add = function (x, y) {
this.eqCtl.innerHTML = x + y + this.tax;
},

subtract = function (x, y) {
this.eqCtl.innerHTML = x – y + this.tax;
},

setVal = function (val) {
this.currNumberCtl.innerHTML = val;
},

setEquation = function (val) {
this.eqCtl.innerHTML = val;
},

//Other functions omitted for brevity

clearNumbers = function () {
this.lastNumber = null;
this.equalsPressed = this.operatorSet = false;

//Set context of "this" to the Calculator object
        //that called clearNumbers()
setVal.call(this, '0');
setEquation.call(this, '');

};

//public members
return {
add: add,
subtract: subtract,
clearNumbers: clearNumbers
};
} ();

Dealing with “this” when using jQuery

Another example of where “this” gets tricky is inside of jQuery event handlers. For example, assume that an init() function is called that adds a click event to DOM elements. What if you want to get to a value of “this” representing the container Calculator object while inside of the jQuery click event handler function? The context of “this” changes to the anchor tag when the click event fires making it challenging. Two potential options that can be used are shown next.

The first option is to store the value of “this” as a variable outside of the click event handler function. By doing this a closure is created and you can still access the original value of “this” when the event fires:

Calculator.prototype = {
//private members
init: function() {
//Option 1 for working with "this"
var calcObject = this;
$('a').click(function () {
//Can't simply use this or $(this)
//since "this" represents the anchor now
//calcObject will represent the Calculator object
calcObject.highlight($(this));
});
},

highlight: function(anchor) {
anchor.toggleClass('highlight');
}
};

Another option is to use an overloaded version of various jQuery event handlers such as on() to pass “this” and make it accessible through the event object:

Calculator.prototype = {
//private members
init: function() {
//Option 2 for working with "this"
//Pass "this" into the on() call
$('a').on('click', { calcObject: this }, function (event) {
//Access the original value of "this"
//through the event object's data property
event.data.calcObject.highlight($(this));
});
},

highlight: function(anchor) {
anchor.toggleClass('highlight');
}
};

Notice that “this” is assigned to an object literal property named calcObject in the on() parameters. Once the click event fires the event object passed to the callback function can be used to get to the data property which exposes the calcObject value that was passed in.

Although working with JavaScript's “this” keyword can be challenging in some scenarios, there are several different techniques that can be used to make it easier to work with. Have any other techniques you like to use? Feel free to leave a comment and share your technique with everyone.

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


This post covers patterns and techniques found in Dan Wahlin's Structuring JavaScript Code course on Pluralsight.com. If you're on Twitter follow Dan at @DanWahlin.

image

Get our content first. In your inbox.

Loading form...

If this message remains, it may be due to cookies being disabled or to an ad blocker.

Contributor

dwahlin