Closure and classes in JavaScript

Although JavaScript is often touted as a class-free language, something very close to classes can be simulated using JavaScript’s support for closure. We’ve looked at C#’s support for closure, but closure is actually even easier to implement in JavaScript.

Suppose we wanted to create a number of bank account objects. One way of doing this is to use a constructor function as we’ve done earlier. We might write something like this:

function OpenAccount(number, balance) {
  this.number = number;
  this.balance = balance;
}

OpenAccount.prototype.deposit = function (money) {
  this.balance += money;
  return this.balance;
}

OpenAccount.prototype.withdraw = function (cash) {
  this.balance -= cash;
  return this.balance;
}

var acct1 = new OpenAccount(1, 1000);

This allows us to create a new account as we’ve done on the last line, where we create an account with number 1 and initial balance of 1000.

While this works, and we can call the deposit and withdraw functions to change the balance, it’s also possible to change the balance directly by writing acct1.balance += 200, which gives us an extra £200 without bothering to use the deposit function. We can do this because the object fields balance and number are public.

In classical OOP, good design says that all data fields should be private, and we should be able to interact with them only by means of interface methods. It looks as though we can’t do this in JavaScript, since there is no ‘private’ keyword (and in fact, no ‘class’ keyword). However, we can do something that amounts to much the same thing using the idea of closure.

Suppose we define the following function:

function account(number, balance) {
  return {
    getNumber: function () {
      return number;
    },

    getBalance: function () {
      return balance;
    },

    deposit: function (money) {
      balance += money;
      return balance;
    },

    withdraw: function (cash) {
      balance -= cash;
      return balance;
    }
  }
}

This function accepts two arguments, just as OpenAccount did, but instead of initializing an object with these values, all it does is return an object containing 4 functions. If you’re used to the classical scoping rules of a language like C#, you might also think this code won’t work. Why? Because we incorporate the function’s parameters ‘balance’ and ‘number’ into the object that is returned. JavaScript’s scoping rules are the same as those of C# and Java, in that function arguments exist only as long as the function is running.

However, because JavaScript implements closure, any function created inside another function gets a copy of the outer function’s internal variables, and this copy exists even after the outer function’s life is over. That’s what closure is; the inner function ‘closes over’ the variables it receives from the outer function and gives these internal copies a separate life of their own.

The key point is that these copies of ‘balance’ and ‘number’ are not data fields of the object returned by the account() function, so there is no direct access to them from the outside. The only way they can be read or written is by means of one of the 4 functions provided. They have, essentially, become private data fields.

To get a feel for how this works, open up a browser (such as Chrome) that supports a console and copy the definition of account() into it. Now try creating an account using this function. Note that since account() returns an object, it’s not a constructor, so you don’t use the ‘new’ operator to create objects. You write simply

var acct1 = account(42, 1000);

First, you can check that the 4 functions work as expected. Try viewing the balance by typing acct1.getBalance(), depositing some money by typing acct1.deposit(500) and so on. You should see that the returned values are what you expect in each case.

Now try accessing the balance directly, without using one of the 4 functions, by typing acct1.balance. You’ll get ‘undefined’ as a result, because the balance isn’t directly accessible.

So where, exactly, are these two data fields ‘number’ and ‘balance’ being stored? To find out, examine the structure of acct1 by typing just acct1 into the console. You’ll see that it’s an Object, so open the Object and look at its insides.

You should see entries for the 4 functions and also the ubiquitous __proto__ object we’ve discussed earlier, but there’s no sign of ‘balance’ or ‘number’.

Open up one of the function entries, say the deposit() function. One of its constituents is <function scope>. Open that, and you’ll see an entry called Closure. Open that, and lo and behold, inside the Closure are our two data fields. So they are being stored after all; they’re just not visible to the outside world, which is just what we want for a proper class.

Those of you who have been following these posts might now notice a possible loophole in all this though. Since JavaScript allows us to add data fields to any object at any time, why can’t we just add a ‘balance’ field to acct1? Let’s try it… Type in acct1.balance = 5000 in an attempt to give our account a free £5000. If you now type just acct1.balance into the console, you’ll get 5000 back as a response, so it seems to have worked.

However, if you type acct1.getBalance(), you’ll find that you still have only the £1000 you started with, and you’ll also find that trying to increase your savings using acct1.deposit() will affect only the original £1000, ignoring the £5000 you tried to add fraudulently.

You can see what’s happened by examining the structure of acct1 in the console again. The new balance field has been added at the top level, but if you dig down into the Closure section of the functions, you’ll find that the original balance is still there, untouched, and it’s that balance that the functions are using.

So as long as your code relies on the interface methods to interact with the data, the acct1 object is secure. That means that if you’ve written your code using this method, any external attempt to hack into it by overriding the private data fields won’t work. Even trying to hack into the object by writing your own function won’t work. If you wrote a function like acct1.fiddle = function(cash) { this.balance += cash; } you’ll find that the fiddle function affects only the top-level balance and not the one in the closure. Leaving off the ‘this’ from ‘this.balance’ doesn’t work either, since there is no ‘balance’ object defined in the scope of the fiddle function.

Advertisements
Post a comment or leave a trackback: Trackback URL.

Trackbacks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: