Revealing Module Pattern and `This`
I read an interesting post the other day by Ben Nadel (@BenNadel). It used a relatively simple cache module to illustrate this
behavior in the revealing module pattern
. It's a good read, and it got me thinking further about public and private methods and this
.
In summary, the post showed the interesting way that method works using the revealing module pattern. Since you are defining function statements and exposing them through an object literal, this
may not always refer to what you'd expect. Specifically, this
will refer to the returned object literal, allowing you to chain methods within that publically exposed API.
I've written a decent amount of JavaScript using the revealing module pattern, so this wasn't exactly news to me. I wasn't shocked that you can write methods that chain using that pattern. Instead, the most thought provoking aspect of the article was the almost-footnote at the end: this doesn't work for private methods. Because this
refers to the object literal, you cannot use it to chain any private or protected methods that you may have defined within the same constructor closure.
Can we fix that? Maybe.
A New SimpleCache Module
To do this, we first need a new SimpleCache module with a private method. I added one public method, init
, which can be called to insert dummy values. For the sake of this example, init
in turn calls two private methods using chaining: initSome
and initMore
. Here's the code:
// Create an instance of our cache and set some keys. Notice that the [new] operator
// is optional since the SimpleCache (and revealing module pattern) doesn't use
// prototypical inheritance. And, we can use method-chaining to set the cache keys.
var cache = SimpleCache().init();
output = document.getElementById('output')
output.innerHTML = cache.get('a') || false;
// ---------------------------------------------------------- //
// ---------------------------------------------------------- //
// I provide a super simple cache container.
function SimpleCache() {
// Create an object without a prototype so that we don't run into any cache-key
// conflicts with native Object.prototype properties.
var cache = Object.create( null );
var priv = {
get: get,
has: has,
remove: remove,
set: set,
init: init,
initSome: initSome,
initMore: initMore
};
var publ = {
get: get.bind(priv),
has: has.bind(priv),
remove: remove.bind(priv),
set: set.bind(priv),
init: init.bind(priv)
};
// Reveal the public API.
return(publ);
// ---
// PUBLIC METHODS.
// ---
// I get the value cached at the given key; or, undefined.
function get( key ) {
return( cache[ key ] );
}
// I check to see if the given key has a cached value.
function has( key ) {
return( key in cache );
}
// I remove the given key (and associated value) from the cache.
// --
// NOTE: Returns [this] for method chaining.
function remove( key ) {
delete( cache[ key ] );
return( publ );
}
// I cache the given value at the given key.
// --
// NOTE: Returns [this] for method chaining.
function set( key, value ) {
cache[ key ] = value;
return( publ );
}
// I initialize the cache with some dummy values
// --
// NOTE: Returns [this] for method chaining.
function init() {
this
.initSome()
.initMore();
return ( publ );
}
// ---
// PRIVATE METHODS
// ---
// I initialize the cache with some dummy values
// --
// NOTE: Returns [this] for method chaining.
// NOTE: Private
function initSome() {
this
.set('a', 'Alice')
.set('b', 'Bob')
.set('c', 'Carol');
return ( this );
}
// I initialize the cache with some more dummy values
// --
// NOTE: Returns [this] for method chaining.
// NOTE: Private
function initMore() {
return this
.set('x', 'foo')
.set('y', 'bar')
.set('z', 'baz');
return ( this );
}
}
See the Pen oXXxWz by Will Mitchell (@wakamoleguy) on CodePen.
A Separate Object, with Bound Public Methods
Notice that all of our methods, both public and private, are defined in the same way as before. Additionally, we create explicit publ
and priv
objects. When we expose our methods on the public object, we bind them to the priv
object. This ensures that internal functions can call each other with chaining, as they will all be bound to the same object.
Lastly, in our public chained methods, we return publ
instead of this
. That way, public users of our SimpleCache are never given a reference to the private object, and instead are always passed the publicly exposed API. Note that public methods can still chain to other public methods.
Is This Actually Useful?
In his original post, Ben questioned the usefulness of chaining private methods. The example above is admittedly contrived just for this purpose. I don't know that I've seen any real case for this in the wild, and the dual-object approach is awfully complex for what it accomplishes. Not to mention the issues this could cause with prototypal inheritance and method lifting. When you bind the public methods, the consumer of the module loses the ability to redirect this
to something else. Is throwing a wrench at your module's users worth it for slightly easier to use private methods?
Defining extra objects and binding functions also increases the memory footprint of your module. Each time you create one of these new SimpleCaches, you are eating up just a little more of your user's browser resources.
So, in the end, while this was an interesting thought experiment, I really cannot see anybody doing this in an actual project. What do you think? Agree or disagree? Have a better way of doing it? Let me know on Twitter.
Cheers!