Name aliases in JavaScript
📅
An object’s properties and methods can be accessed, at least, in two ways:
console.log( "Hello" );
// is the same as
console[ "log" ]( "Hello" );
Where would I use this?
The first places I started using this feature were closures. For example, in a page with a main
element, that contains a header
, one or more section
s, and a footer
, one might program:
( function( ) {
const main = document.querySelector( "main" );
const mainHeader = main.querySelector( "header" );
const mainFirstSection = main.querySelector( "section" );
const mainFooter = main.querySelector( "footer" );
// TODO: Do something with these constants
} )( );
However, it is equally valid to declare an alias for the querySelector
function’s name and use it as described before:
( function( querySelectorFN ) {
const main = document[ querySelectorFN ]( "main" );
const mainHeader = main[ querySelectorFN ]( "header" );
const mainFirstSection = main[ querySelectorFN ]( "section" );
const mainFooter = main[ querySelectorFN ]( "footer" );
// TODO: Do something with these constants
} )( "querySelector" );
Why would I do that?
The main reason for this is minification. Amongst other things, a minifier will change variable names to shorter ones, producing a smaller script with the same functionality:
( function( q ) {
const m = document[ q ]( "main" );
const h = m[ q ]( "header" );
const s = m[ q ]( "section" );
const f = m[ q ]( "footer" );
// TODO: Do something with these constants
} )( "querySelector" );
Passing the first closure through JSCompress, a minifier which uses UglifyJS 3 at the time of writting this text, we get the following stats:
52.16% compression, saving 0.14 kb
The same process on the aliased closure gives us:
68.37% compression, saving 0.22 kb
These percentages are not comparable. Minifiers also remove comments and non-functional spaces. Styling choices in the second code block, like using verbose variable names and spaces inside brackets, contribute to the uncompressed code size without adding functionalty. In regards to minification, this is bloat and does not affect the outcome.
A fairer measure of the gains obtained is comparig the resulting output. The first block is minified to 133 characters, and the second block goes down to 105.
How do I put it in practice?
In principle, it would be a sound strategy to identify the most used properties and methods in a closure and declare aliases for their names. The usual suspects are querySelector
, querySelectorAll
, className
, classList
, addEventListener
… but each closure requires independent analysis.
We can obtain the same compression gains applying the same analysis and strategy for global objects, if they appear frequently in the closure: window
, document
, navigator
…
Common values can be also aliased. For example, if your code has to check for types, type names can be aliased and used in all those typeof
verifications.
Is there a catch?
There are a some problems that must be observed when using this feature.
Readability
It can be reasonably argued that the code is more difficult to read and interpret.
Anyone familiar with JS will instantly know what document.querySelector
does, but the same cannot be said for document[ querySelectorFN ]
. One can remember, but recalling that information might require time. Worse, one might guess at the meaning of querySelectorFN
, and miss where the original programmer thought it was oh-so-clever to change its value to querySelector
or querySelectorAll
, depending on some criteria. I wish this were a hypothetical.
Out-of-the-box syntax highlighting is lost, as evidenced by the differences in colouring between the first code block and the aliased variant. Other helpful features, like contextual information in code editors, may be impaired, too.
I cannot stress enough how important all of this can be.
High-level programming languages are designed for people. The code ought to be run in a machine, and do so efficiently, but this is not machine code. The lines of code are meant to be read, understood and manipulated by humans. Anyone conciously ignoring this purpose is misusing the technology.
Maintenance
The code will have to be maintained. Change the code and the usage count of any alias might change. There may be some new ones that ought to be introduced. Basically, we have just put upon ourselves the obligation to keep up the pro-minification strategy.
Again, all of this is done by humans. Even if you manage to automate it, the automation will have to be programmed and maintained by a person. This burden can be shifted, managed, and perhaps aliviated, but it is unavoidable.
Not memory use, possibly
It is a bit weird to mention here something that is (maybe) not a problem, but I find the reasoning to be quite interesting. Also, perhaps the chain of thought is wrong and exposing it might provide a chance to realise it and correct it.
There is an issue with closures in general that it is likely to occur but may not have a real impact.
Objects declared in a closure take up space in memory until they are no longer used. Usually, this means until the closure is inactive but, if it includes periodic or asynchronous code executions, it may be considered always active, for all intents and purposes.
Smart garbage collectors will manage to liberate the memory occupied by some variables and constants, if it can be determined that they are not going to be used. However, the more frequent the method or property is called, the more likely it will be used in an always-active part of the code. Since the number of occurences in the code is the basic criteria to create an alias, it stands to reason that that this strategy will be prone to create the aforementioned situation.
Granted, we are talking about relatively small strings and their memory consumption should be negligible, but this can only be evaluated on a case-by-case basis.
Not speed, probably
Again, I think it is interesting to know how to arrive at this conclusion.
Unlike the previous issue, we can evaluate the theoretical impact on speed a priori. To that effect, we can devise a simple test that will do the same operation a number of times, with and without using aliases.
( function( appendChildFN ) {
const NUM_ELEMENTS = 1e6;
const paragraphs1 = [];
const paragraphs2 = [];
for ( let p = NUM_ELEMENTS - 1; p >= 0; p = p - 1 ) {
const value = Math.random();
paragraphs1[ p ] = document.createElement( "p" );
paragraphs1[ p ].innerHTML = value;
paragraphs2[ p ] = document.createElement( "p" );
paragraphs2[ p ].innerHTML = value;
}
const container = document.createElement( "div" );
container.innerHTML = "";
console.time( "alias" );
for ( let p = NUM_ELEMENTS - 1; p >= 0; p = p - 1 ) {
container[ appendChildFN ]( paragraphs1[ p ] );
}
console.timeEnd( "alias" );
container.innerHTML = "";
console.time( "standard" );
for ( let p = NUM_ELEMENTS - 1; p >= 0; p = p - 1 ) {
container.appendChild( paragraphs2[ p ] );
}
console.timeEnd( "standard" );
} )( "appendChild" );
It is far from perfect and comprehensive, but even changing the order, or what array of elements each variant uses, the results are always similar.
On Firefox 105:
alias: 78ms - timer finished
standard: 81ms - timer finished
On Chrome 105:
alias: 267.902099609375 ms
standard: 269.5029296875 ms
I will be the first to point out that this only shows that there is no significant impact in the test. Performace should be measured in production, or in as close as we can get to that environment, but in the absence of that possibility this might suffice.
One last thing
This whole praxis is aimed at minification. All the minification tools I use can produce source maps, so pretty please, with sugar on top, publish them whenever possible. As I said earlier, high-level programming languages are designed for people. On the same principle, let others be able to read properly what you coded.