Using closures as backing fields for ECMAScript5 properties
One of the things that ECMAScript version 5 brings to the JavaScript world is the ability to define "getter" and "setter" properties, i.e., the ability to define object members that look like regular fields to clients but are really member methods. The best way to understand this is with an example. Here goes:
var person = Object.create({}, {
name: {
get: function() {
print("name.get");
return "foo";
},
set: function(v) {
print("name.set - " + v);
},
enumerable: true,
configurable: false
}
});
person.name = "boo"; // calls the "set" function above
print(person.name); // calls the "get" function above
We've defined an object called "person" above with one property called "name". We've defined "get" and "set" functions which will cause the JavaScript runtime to call the respective methods whenever an attempt is made to access the property or set a value for it. This works exactly the same way as how properties work in C#. If you run the snippet given above here's the output we get:
name.set - boo
name.get
foo
You'll note that I really haven't actually saved the value being passed as a parameter to the "set" function of the "name" property above. In a real program you'd typically want to save this in a backing field of some sort. Now, the question is, where do we save this? One approach could be to do something like the following:
var person = Object.create({}, {
nameVal: {
value: null,
enumerable: false,
writable: true,
configurable: false
},
name: {
get: function() {
return this.nameVal;
},
set: function(v) {
this.nameVal = v;
},
enumerable: true,
configurable: false
}
});
person.name = "boo";
print(person.name);
While this works, it does however defeat the purpose of defining separate getter and setter methods since the client of this object is anyway able to directly write to "nameVal". We need a mechanism of somehow hiding the backing field so that it is accessible only from the getter/setter methods and not through an object instance.
Enter closures! It turns out that the descriptors for a property can actually be defined dynamically at runtime. It can be for instance, be returned from a function. We should be able to leverage this to define a self-calling function that results in the creation of a separate execution scope where the backing field for the property can be stored as a part of the closure for the scope. Again, an example should make the idea clear.
var person = Object.create({}, {
name: (function() {
var nv = ""; // backing store in closure
//
// the property descriptor is actually
// returned from this function
//
return {
get: function() {
print("name.get");
return nv;
},
set: function(v) {
print("name.set");
nv = v;
},
enumerable: true,
configurable: false
};
})()
});
person.name = "foo";
print(person.name);
This snippet is identical to the first example except that the property descriptor for "name" is being returned from an anonymous self-calling function. The backing store for the property is stored in a variable that becomes a part of the closure for the setter and the getter methods. This achieves our goal of creating a backing field that is accessible only from the getter/setter routines.
The dynamism of JavaScript can get a bit unwieldy sometimes but this is one occasion where being able to dynamically generate the property descriptor ends up providing an elegant solution to the problem of creating backing stores for member properties!