Home / AJAX

Local Variable and Function Renaming

RSS
Modified on 2009/12/14 18:03 by Stephen Walther Categorized as Uncategorized
Local function and variable names are renamed to shorter names, while global functions are left alone. Function scope chains are respected, so global and outer variable references are carried through without interruption or interference. Within a local scope, variables and functions are renamed starting with lower-case letters, then upper-case letters, then combinations thereof. Those with the most references are named first, thereby attempting to make the biggest gains for those fields that are referenced most frequently.

Another aspect of AjaxMin is reference counting and tracking. This is used to remove some unused code. For example, if a function defines arguments that are never referenced, they are removed from the tail of the argument list:

function DivideTwoNumbers(numerator, denominator, unsedparameter )
{
    return numerator / denominator;
}

Gets reduced to:

function a(a,b){return a/b}

Because the last parameter is never referenced, it is not part of the output. If an unused parameter is followed by a referenced parameter in the list, it is not removed because it affects the order of the used parameters. In JavaScript it is perfectly normal to pass fewer or more parameters than the function formally declares – only the order matters.

Scope chains are analyzed to find unreachable code in the form of local functions that are not actually called. Those functions are removed from the resulting output. In addition to the normal function that simply isn’t called from anywhere, the analysis also works with recursive functions (functions that call themselves) and chains of functions that call each other but aren’t called from anywhere else. A global function is never removed because it may be called from other modules outside the file being minified. Function expressions are assumed to be referenced by their containing scope.

The parameter to the catch statement provides an opportunity to write code that behaves differently in different browsers. In Internet Explorer, catch parameters are simply variables defined within the containing function scope, so code like this is perfectly acceptable:

function foo()
{
    try
    {
        // do something that might error
    }
    catch(e)
    {
        // handle the error
    }
    alert(e);
}

All other browsers, however, would throw a script error because e is undefined outside the catch block. This may seem like a trivial difference, but when using the variable renaming feature, we have to be sure we’re pointing to the right field to produce the desired results. For instance, take the code below:

var e = "outer";
function foo()
{
    try
    {
        // do something that might error
    }
    catch(e)
    {
        // handle the error
    }
    alert(e);
}

In all browsers but IE, the alert function in the above code will always display “outer,” whether or not there was an error and the catch block was executed. In IE, this code will display “object Error” if there was an error, and “undefined” is there wasn’t. If the outer “e” variable gets renamed to “a” and the catch argument gets renamed to “b,” then the alert parameter needs to be “a” to reference the proper value in non-IE browsers. But for Internet Explorer – and only for IE – the alert parameter needs to be “b.” It is generally a bad idea to code a function using try/catch statements such that the catch argument is the same name as a variables referenced outside the catch block (as in the above example). Such a pattern will behave differently in different browsers and should be avoided. As an aid to developers, an error will be thrown if Microsoft Ajax Minifier detects such a situation:

Possible coding error: Ambiguous catch identifier 'e'. Cross-browser behavior difference.
At line 8, col 10-11: e

Switch case statements will also be manipulated. For instance, if the final case/default statement in the switch ends with a “break” statement, that statement will be removed, since control flow will fall out of the switch block after the last statement in the last case is executed anyway. If there is a “default” case and it only contains a break statement, it will be removed. If there is no default case (or if an insignificant default case is removed), then any other cases that only contain a break statement will also be removed as insignificant. The exceptions are if the break statements break to a label that is not the containing switch statement.

Frequently-used literals can be combined into local variables when the –LITERALS:COMBINE option is used. For instance:

function foo(p)
{
    p[0].style.display = "block";
    p[1].style.display = "block";
    p[2].style.display = "block";
    p[3].style.display = "block";
    p[4].style.display = "block";
}

Would get reduced to: function foo(p) { var a="block"; p0.style.display=a; p1.style.display=a; p2.style.display=a; p3.style.display=a; p4.style.display=a } The literals that may be combined are: true, false, null, numeric values, and string values. Be aware that only literals within a local scope or tree of local scopes will be combined in this way. No global variables will be created by Microsoft Ajax Minifier to eliminate the possibility of colliding with other global values. If the same string literal is frequently used across global functions, for example, the code will not create a global variable shared by all the functions.

One possible problem with using the –LITERALS:COMBINE option is that the algorithm affects the entropy of the resulting JS file in such a way that further compression by the web server (for example, using over-the-wire gzip) may not be as effective as if applied against code that doesn’t combine the literals. If you use the –LITERALS:COMBINE option, be sure to test your file sizes after compression against the normal, un-combined results as well. Although the –LITERALS:COMBINE results may be significantly smaller before compression, they might be larger than the regular results after compression.

One aspect of minification can be seen as either good or bad, depending on whether or not you’re trying to debug the deployed code. Variable-renamed code is very difficult to read – it could be seen as a sort of mild (but maddening) form of obfuscation. First, all the comments and semantic names are removed and all the code is on a single line. Then add that each function scope starts its renamed variables over again from “a,” and trust me – stepping through this kind of code in a debugger will drive you insane.