JavaScript for C# developers: the Module Pattern (part 3)

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.

StopwatchPrivate 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.)

Album cover for MezzanineNow playing:
Massive Attack - Teardrop
(from Mezzanine)

Loading similar posts...   Loading links to posts on similar topics...

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.

  •  Emphasize with italics: surround word with underscores _emphasis_
  •  Emphasize strongly: surround word with double-asterisks **strong**
  •  Link: surround text with square brackets, url with parentheses [text](url)
  •  Inline code: surround text with backticks `IEnumerable`
  •  Unordered list: start each line with an asterisk, space * an item
  •  Ordered list: start each line with a digit, period, space 1. an item
  •  Insert code block: start each line with four spaces
  •  Insert blockquote: start each line with right-angle-bracket, space > Now is the time...
Preview of response