6 years later and this advice still resonates, with the right message too. If your “thing” is what we call today “low-level Java Script”, perhaps you might read carefully.
[2014 APR 13]
Premature Initialization is the source of all evil. Yes? OK? Fine then. That is a bombastic blog statement out of the way. Now let’s dive into some serious JS.
First let me be clear: There is hardly anything new in this post. This subject has been presented before. Still, I see too much of a JavaScript code, these days, where this simple programming idiom, is somehow still not in use. So why not revisit this issue for the sake of new generations.? OK, then let’s start.
We will not start with initialization but with modularization. One JavaScript idiom in widespread use. Yes, I am sure anyone will agree that JavaScript closures are in widespread use these days.
Here is a trivial one:
1 2 3 4 5 6 7 |
/* closure as a JavaScript "module" */ (function (global,undefined) { var hidden = true ; global.public = function () { return hidden; } ; }(this)); |
I hope we do not have to debate here why do we use JS closures to implement kind-of-a-modules, etc. The above is by far the most prevailing pattern for the creation of a kind-of-a-module using JS. It is based on the definition and immediate calling of the anonymous function. Right?
Well … not exactly “right”. Here is the problem. A lot of very happy JS developers are using this pattern for implementing initialize-once-use-later “clever” mechanism:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/* closure for initialization */ var is4d = function ({ /* initialized on first call rx stays hidden in this closure */ var rx = (/^[0-9]{4}$/); /* create and return the actual function to be called on *all* subsequent calls */ return function (val) { /* use rx which is initialized and hidden */ return !! ("" + val).match(rx); }; /* the function above is assigned to is4d */ }(this)); /* execute the closure immediately! */ |
Above we have an immediate initialization of the Rx. That is because closure is just an anonymous function that is immediately called (executed). So inside the closure, we prepare all the static or constant content we will need for the actual worker function to use when subsequently called. And because all of this initialized and hidden “closure” stuff stays hidden, you have this warm feeling about being clever JavaScript uber-geeks, which is always a “good thing”, is it not?
Very quickly this becomes your habit and you happily sprinkle this “awesomeness” all-around your “awesome” code. But herein lies your problem, as there are problems with this pattern, overzealous usage.
First of all this pattern if used “all-around a place, by everyone” (meaning by “tons” of libraries included) slows down the loading time of pages where your “awesome” code is loaded too. This is simply because immediately after the code gets loaded, there is a lot of JS work happening, which is executed all-around your little or large closures. before any event has a chance to be dispatched for example. Therefore (and simply) page loading time can be vastly expanded, in presence of a lot of complex initialization code inside a lot of closures. And remember, not just in your code but in numerous jQuery plugins and such, that you are including in your “awesome”, single page, web app.
Second (big) issue. This mechanism can not be used to reference other parts of the object which it is part of. Quick explanation:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var ToolBox = { /* Reg Exp's that will be used by all sorts of tools in here Also, future optimized Rx might be added */ rx : [ /^[0-9]{4}$/, /[0-9][0-9][0-9][0-9]/ ], is4d: (function (val) { /* use rx[0] for now, maybe [1] in some future implementation */ rx = ToolBox.rx[0] ; return function (val) { return !! ("" + val).match(rx);} }()) } |
The above might be a good example of programming resilient to change. But JavaScript will not do it and instead, it will “explain” to you, what is wrong:
1 2 3 4 5 |
/* TypeError Number: 5007 Description: Unable to get property "rx" of undefined or null reference */ |
Above is because you are immediately executing a closure which is how you thought you will code your ToolBox.is4d() function. So from the closure, you are calling ToolBox.rx. Which is “no-can-do” because ToolBox at that moment in time is not fully made yet. It is simply null
.
Third problem. The closure mechanism presents difficulties when referencing the variables or objects external to the closure.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/* closure used wrongly for pre-mature initialization */ var is4d = function ({ /* initialize immediately on loading this code*/ var rx = (/^[0-9]{4}$/), errmsg ; /* reference object external to this closure ? string_storage() must exist and must behave and must be unchanged! if the following fails it will fail in the middle of the loading time which makes it very difficult to spot, especially by your colleagues it this is in an external files JS engine will refuse to load it... */ assert( isString( errmsg = string_storage( MSGID.NOT_A_STRING ) ); return function (val) { assert( isString(val), errmsg ) ; return !! ("" + val).match(rx); }; }()); /* execute the closure immediately */ |
The above code is not “adorned” with error checking, because of usage of assert() on the required type. Clever stuff? Robust coding?
I beg to differ. This code also depends on an external library and thus references some external string storage in order to prepare the message for the eventuality it might be needed inside the worker. And yes, in turn, this string storage, once was, but then it might or might not be defined when needed for a variety of reasons. It is likely defined in another module not made by you. Or even bought by your company. That js file might be simply not included before this code, by mistake. Or someone somewhere, has decided after some time to quietly insert a “modernised” new version? So perhaps MSGID constants are not used any more, etc. If any of these is true, your code will stop at the javascript file loading initialization time. Which is the worst time to stop and notoriously difficult to debug. The point I am making here is not that your code is lousy, the point is that it is lousy and it also happens before somebody else can spot it in a normal way. Teamwork, remember?
Your good company has decided to send you to me. To the “Clinic for premature Initialization”. Welcome. Relax.
There is a medicine for you. It is effective and it works. Stop shaking, calm down and think. The Source of the evil was that you did initialization prematurely. Yes, yes. As everyone else does and they are not punished (yet). But that is not the point.
The initialization mechanism you have used is not lazy initialization. It simply happens too soon! It is even not an initialization pattern! So … Just say no to closure as an initialization pattern. Do not confuse initialization and modularization. Enough of this long patronizing and let us give you the solution and medicine:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/* lazy initialization pattern */ function is4d(val) { /* this version is used ONLY on the first call. this code is visited only on first call, NOT BEFORE ! */ var rx = (/^[0-9]{4}$/); /* function body is a closure, therefore rx will not be garbage collected (aka destroyed) because it is referenced by the is4d() redefined. Now. REDEFINE the is4d() function itself Only once and only on the first call This version of is4d() gets used for first and every other call */ is4d = function (val) { return !! val.match(rx); }; /* execute the redefined version and return the result */ return is4d(val); /* on the FIRST call after we exit here REDEFINED one replaces the first version */ } |
Phew! That is a lot of comments for such a little function. Ultimately this implementation pattern depends on the ability of JavaScript to redefined the function inside its own body. I can not overstate how important this pattern is. It could (and should) be used everywhere in your code where immediate initialization happens. If it has not hurt you yesterday, it will tomorrow. So remove it today. Back to the code. This also is a solution to a second problem we presented.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var ToolBox = { rx: [/^[0-9]{4}$/, /[0-9][0-9][0-9][0-9]/], /* Lazy initialization as a part of object literal creation */ is4d: function (val) { /* rx is referenced by later inner redefinition */ var rx = ToolBox.rx[0]; /* ToolBox does exist here */ /* redefine the is4d() */ is4d = function (val) { return !!val.match(rx); }; /* this line only on first call */ return is4d(val); } } |
Closures and Lazy Init Pattern combined
But. “There is no silver bullet” as it is already known. This pattern if used everywhere can lead to a lot of variables and object which GC might (or will) not be able to swipe, thus potentially our lazy initialized uber-clever-code might keep a lot of memory in use.
Here one can use an excellent combination of lazy initialization and standard closures. Here is the problem again. Watch closely.
1 2 3 4 5 6 7 8 |
var sum= function() { var a = 0,b = 1,c = 2,d = 3,e = 4, r = complex_operation(a,b,c,d,e) ; sum= function() { return r; }; return sum(); }; |
Above, it is very likely that GC will not know1 that a,b,c,d,e can be swiped. One simply can not be sure. Instead of wondering will it or will it not, just use a standard closure. The key is to encapsulate the closure inside the lazy initialization!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var sum= function() { /* standard closure encapsulates the complex initialization but crucially inside the lazy initialization mechanism ! */ var result = (function() { var a = 0,b = 1,c = 2,d = 3,e = 4, r = complex_operation(a,b,c,d,e); return r ; })(); /* lazy init pattern starts here */ sum = function() { return result; }; return sum(); }; |
Voila! All of your to-be-temporaries are now temporaries indeed. When the anonymous closure function exits they are all swiped; just the result is preserved.
Conclusion
Examples here are deliberately simple. In order to explain the Lazy Init aka just-in-time mechanism.
It is important to understand the implications when using the “self-igniting” mechanism of the anonymous function closures-as-modules. Use both patterns but know when to use which one. And above all: Do not confuse the two.
1: Although these days modern JS engines do contain some pretty clever GC’s.
3 thoughts on “An JavaScript Clinic for premature Initalization”
Nice article, thanks. I found a small typo:
@John thanks for being a patient reader. Typo fixed.
Uhh.. uhhhh … serious subject.