JavaScript, inner functions and private members
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?