Now that we’ve seen the simple module pattern as well as ways to augment it, we should take a look at one final piece of the puzzle.
Private fields, as we saw in the last installment, can be a real issue. Sometimes, dammit, we’d just like to refer to that private local variable when we’re augmenting a module object. Just a peek you understand, and we’d make it private again immediately we’re done augmenting, so that the code using the module object doesn’t see this private variable. Unfortunately, given the way we’ve implemented privacy through closures this remains a bit of a problem.
Let’s see what we could do given a blank slate. Let’s create an object called secretData
and make its properties the internal data we want to save and to share amongst our augmentation code. Obviously, we’d have to make this a normal property so that other code could use it with the augmentation pattern. Here’s the changed stopwatch:
var stopwatch = (function () { var $sd = {}, start = function () { $sd.startTime = $sd.now(); }, stop = function () { var elapsedTime = $sd.peekElapsedTime(); $sd.startTime = -1; return elapsedTime; }; $sd.startTime = -1; $sd.now = function () { return +new Date(); }; $sd.peekElapsedTime = function () { if ($sd.startTime === -1) { return 0; } return $sd.now() - $sd.startTime; }; return { start: start, stop: stop, secretData: $sd }; }());
Nothing to it so far: for the first change I declare a local variable called $sd
and then add startTime
and now()
to that object. I also refactored the code a little bit so that there’s a new function that can just calculate the elapsed time. Since we’re going to share this behavior, we might as well isolate the complicated calculation into its own function. The returned object now has a new property called secretData
(which is anything but, at the moment) which is a reference to this local variable. The local variable is of course hidden by the closure.
Onto the tight augmentation code:
// Augmented stopwatch var stopwatch = (function (sw) { var $sd = sw.secretData; $sd.laptimes = []; $sd.oldStop = sw.stop; sw.stop = function () { sw.lap(); $sd.oldStop(); }; sw.lap = function () { $sd.laptimes.push($sd.peekElapsedTime()); }; sw.reportLaps = function () { var laps = $sd.laptimes; $sd.laptimes = []; return laps; }; return sw; }(stopwatch));
I first make a copy of the secretData
property and then use that copy throughout for any local variables I need in the closure. Other than that, the code for lap()
is much simpler (since I’ve made use of the method that returns the elapsed time), as is the overridden stop()
method. The returned object still has the very public secretData
property of course.
Now the fun bit. After the module object has been finally augmented, we call:
delete stopwatch.secretData;
Whoa. What this does is to delete the secretData
property completely. But, notice something else: both closures created by the module pattern have made copies of the secret object already (they both called this copy $sd
). The code in the closures still functions just as before since it makes no reference to the secretData
property (except when executing the anonymous function to create the closure in the first place). Admittedly with some shenanigans, we’ve created some shared secret data across several closures.
If you don’t like the call to delete
there, just create a method in the original unaugmented code (hideSecretData
) that “cleans up” the public references to the secret data and call that instead. This makes it a little more more readable:
var stopwatch = (function () { var $sd = {}, start = function () { $sd.startTime = $sd.now(); }, stop = function () { var elapsedTime = $sd.peekElapsedTime(); $sd.startTime = -1; return elapsedTime; }, hideSecretData = function () { delete this.secretData; delete this.hideSecretData; }; $sd.startTime = -1; $sd.now = function () { return +new Date(); }; $sd.peekElapsedTime = function () { if ($sd.startTime === -1) { return 0; } return $sd.now() - $sd.startTime; }; return { start: start, stop: stop, secretData: $sd, hideSecretData: hideSecretData }; }());
and then all you need to call is this to make the intent of the code clearer:
stopwatch.hideSecretData();
After this, not only will the secret data have been hidden, but also the method that hid it will have vanished.
(Part 1, Part 2 of this series.)
Now playing:
Massive Attack - Teardrop
(from Mezzanine)
No Responses
Feel free to add a comment...
Leave a response
Note: some MarkDown is allowed, but HTML is not. Expand to show what's available.
_emphasis_
**strong**
[text](url)
`IEnumerable`
* an item
1. an item
> Now is the time...
Preview of response