In our last installment in this short JavaScript series we
took a look at closures. In some of the examples, we saw functions being
declared (and returned) inside other functions. The capability of declaring
a function inside another one is not common to all languages — C#
only added this ability in version 2.0, via anonymous delegates.
Here's an example of inner functions in use.
function printPriceLong(name, price, quantity, currency) {
var formatCurrency = function(value) {
return value + ' ' + currency;
};
return 'Item: ' + name + '\n' +
'Unit price: ' + formatCurrency(price) + '\n' +
'Quantity: ' + quantity + '\n' +
'TOTAL: ' + formatCurrency(price * quantity);
}
alert( printPriceLong('100g Toblerone bar', 2.09, 3, 'USD') );
/* =>
Item: 100g Toblerone bar
Unit price: 2.09 USD
Quantity: 3
TOTAL: 6.27 USD
*/
If we try to call formatCurrency
from anywhere outside
of printPriceLong
we are going to cause an error because
formatCurrency
is scoped only inside its parent function.
In this example we can also see closures in action once again. The
currency
value is referenced inside formatCurrency
but it's declared in it's parent. It's a short-lived closure, mind you,
because we are not returning that inner function. It's discarded as soon
as the parent function exits.
Who said JavaScript objects can't have private members?
Developers sometimes get upset when they realize that anyone has
read and write access to the fields and methods of their JavaScript objects.
Most of us are used to work with languages that allow us to declare
some of our object's members out of reach for the calling code. We
say that these members are private and we don't want anyone changing
or even seeing them.
Well, this is not exactly true. If you must have private members in
your JavaScript objects, you can. Inner functions and closures will
come to our rescue.
Let's build on our previous example. Let's create an OrderItem
object that will hold those values (name, price, etc.) Let's assume we
do not want anyone changing the object's price without changing the
currency at the same time (to ensure some level of consistency.) We
could code our object like this:
function OrderItem(productName, price, quantity, currency) {
//regular properties
this.name = productName;
this.quantity = quantity;
//read accessors
this.getCurrency = function(){ return currency; };
this.getPrice = function(){ return price; };
//write accessor
this.setPrice = function(newPrice, newCurrency){
if(typeof newPrice !== 'number' || newPrice < 0){
throw { message:'invalid price' };
}
if(typeof newCurrency !== 'string'){
throw { message:'invalid currency' };
}
price = newPrice;
currency = newCurrency;
};
//a private function
var formatCurrency = function(value) {
return value + ' ' + currency;
};
//methods that need private members
this.getUnitPriceString = function(){
return formatCurrency(price);
};
this.getTotalPriceString = function(){
return formatCurrency(price * quantity);
};
}
OrderItem.prototype = {
//overriding the string representation of the object
toString: function(){
return 'Item: ' + this.name + '\n' +
'Unit price: ' + this.getUnitPriceString() + '\n' +
'Quantity: ' + this.quantity + '\n' +
'TOTAL: ' + this.getTotalPriceString();
}
};
That seems a bit long, but hopefully we can understand what's going on here.
We are letting name
and quantity
be regular
read/write properties but we never defined properties for price
or currency
. We made those two values accessible via the
getPrice
and getCurrency
methods, respectively.
The trick here is that both getPrice
and getCurrency
are defined inside our constructor
function so they have access to the local variables price
and currency
. They have access to these variables even
after the constructor returns (thank you closures.)
The same can be said for the setPrice
method. We
will use this method when we need to change the object's price.
It will force us to also provide a currency.
I'll leave the explanation of the methods getUnitPriceString
and getTotalPriceString
as an exercise for you.
Let's instantiate one of these objects and see it in action.
var item = new OrderItem('100g Toblerone bar', 2.09, 3, 'USD');
//public methods:
alert( item.getUnitPriceString() );
// => '2.09 USD'
alert( item.getTotalPriceString() );
// => '6.27 USD'
alert(item); //this will use the toString() method
/* =>
Item: 100g Toblerone bar
Unit price: 2.09 USD
Quantity: 3
TOTAL: 6.27 USD
*/
//changing private fields
item.setPrice(1.11, 'EUR');
alert( item );
/* =>
Item: 100g Toblerone bar
Unit price: 1.11 EUR <-- it worked!
Quantity: 3
TOTAL: 3.33 EUR
*/
//proving that price is not a field
item.price = '5.00';
alert( item.getUnitPriceString() );
// => '1.11 EUR' <-- Gotcha, smart pants!
item.setPrice(2);
//ERROR: message = 'invalid currency'
alert( item.formatCurrency(1.23) );
//ERROR: item.formatCurrency is not a function
And what am I supposed to do with this information?
I have yet to find the need to use private members in my
JavaScript objects. Maybe that's because I am not shipping
any JavaScript library with complex enough objects.
I think it's nice to know that you can create those off-limits
values in your object. Hopefully when the need for such thing
arises, we won't just say Oh, no! Can't do that!.
What about you? Have you found a use for private members in
your JavaScript code? How did you get around or implemented it?