Minification tricks

2010-08-10 15:27:56

So in light of the js1k contest I'd like to give you some tips. These fall in four categories: white ninja, grey ninja, black ninja and scurvy pirate. Some of it I use on a daily basis, some of it is pretty much unique to these kind of contests and code golfs :p

White Ninja

I consider these okay hacks (ab)using valid parts of javascript to make your script shorter. Some are probably known to you, some might still be new.

- Short names

This should be a given, but short variable and functions make for shorter source code. Running out of your alphabet characters? Don't forget $ and _ are valid as well. Still need more? Head over to the black ninja part.

- Whitespace

Likewise, this should go without saying. Javascript doesn't care about whitespace. In fact, it is stripped to it's bare requirements. For this compo, only use whitespace to separate two otherwise indistinguishable tokens from each other. Use semi-colons to replace newlines (basically, any submission should be on a single line) because they are often two bytes, not one.

- Cache objects

If you use a certain object more than once, like document, cache it in a single named variable. The extra bytes for the assignment are won by not having "document" twice in your source. Be careful about contexts. While it's fine to cache document, you cannot blatantly cache document.getElementById because it will screw up the context of the gebi method (I won't go into details here).

- Cache property names

Certain properties (or methods) are used more often. Note that as far as Javascript is concerned, is equivalent to foo['bar']. The only difference is that the name of the property can be dynamic in the second example. And you can use keywords (ES5) in the second example. Also note that it's perfectly valid to call the result; foo[x](1,2,3);.

- Literals

Javascript has a literal notation for objects, arrays and regular expressions. For objects it is curly brace: {key:value}, for arrays it is square brackets: [a,b,c] and for regex it is forward slash: /abc/flags.

- Getting canvas

You are getting canvas for a nominal fee. The tag exists and is always the first element of the body. You can obviously get that element using document.getElementById('c'). You could also use the children array, document.body.childNodes[0]. There are a few other options, like getElementsByTagName or getElementsByClassName, but they are longer :) Please note that for the js1k compo, it is not allowed to access the auto-global c because Firefox does not support that nasty feature. In standards mode.

- Window is global

Whenever you are accessing window, you are accessing the global object. So doing window.addEventListener is the same as doing addEventListener (except for minor details).

- Hex literals

You can write numbers as hexadecimal literals. You don't usually save anything though because the literal must be prefixed with 0x or 0X.

- Increment / decrement

The shortest way of increasing or decreasing a variable by one is with the ++ and -- operator. ++a; a++; --a; a--; If you need to change it by more than one you can use the combined assignment operators for most operations... a += 5; b-= 3; c /= 4; d *= 2; e %= 8; f ^= 3; Writing a += 5;a = a + 5;. The same goes for the other examples.

- Shorter number notation

Numbers can be written in all kinds of forms. Floats that start with a zero integer (0.4, 0.6321) can have the leading zero removed: .5 .32451. Numbers that have a large magnitude can have a number of zeroes replaced by e: 50000 === 5e4, the e is allowed as a literal (negative as well).

- Function declarations vs expressions

Javascript has three different ways of creating a new function. The first one you probably learned is the function declaration. It goes like function name(a,b){ code }. This hoists the function and makes it available for the first piece of code executed, even if the function declaration comes after it. The name of the function is defined locally in the scope it was declared in. The second method is an anonymous function expression: var f = function(a,b){ code };. A new function is created as an object and assigned to the variable f. The third looks like a combination but isn't, it's just an expression with added semantics: var f = function g(a,b){ code };, the named function expression. The special rule here is that the g variable should only be known to the code within the function and not outside. However, some browsers mess up these semantics and it is not advised to make use of these (furthermore they waste bytes, so there's no point ;)). Note that function(){} can be passed on as an argument direction, called immediately and behaves as an object. Also note that your source code (or function code) may not start with "function" or "{", not counting whitespace.

Grey Ninja

- Prevent blocks

Blocks, those snippets of code surrounded by { and } to group, can be prevented by using the comma operator. if (x) { a; b; c; } is the same as if (x) a,b,c; as long as a,b and c are "expression statements"... So an assignment is okay but a function declaration is not.

- The bit-wise NOT operator

While the ! inverts a value to a boolean true or false, the ~ inverts a number to it's bit-wise opposite. It literally flips every bit of a number. The special property of this operator is that it will only return 0 if the argument is -1. It will return something else for all other arguments. So to check whether the result of a method is -1, you can use !~result.

- Flooring and rounding

You may be used to the Math library but when going short, you need every byte you can get. Internally, most bit-wise operators actually do a 32bit cast first. This means that whenever you apply a float to a bit-wise operator, the result will be the operation of the floored argument instead. That's why these are the same for all numbers: Math.floor(x), ~~x, x|0. To round, simply add .5 to the argument first.

- Ternary operator

You can write if-then-else in your code to the ternary operator ?:. So if(x)foo;else bar; would become x?foo:bar;, saving you a whopping 8 bytes and giving you two bonus points for sexiness. It's also an expression now so you can use it with the comma operator trick. Only works if foo and bar are expressions though.

- && and ||

These operators have a special property in Javascript. The && immediately returns the left hand side if it is falsy. The || immediately returns the left hand side of it is not falsy. Note that it doesn't cast the result to true or false! So 5||10 returns 5 while 0&&20 returns 0. These can often be used to replace an if statement.

- Multiple var statements

The var statements can be combined into a single var statement. Note that for the js1k compo, you don't actually have to declare your globals unless your specific code needs to (you need to assign it a value at least once before reading it). So if you are going to use var, note that you can chain them together, saving the multiple var keywords. var a,b,c; var a=2,b,c=3,d;

- Cheap local variables

If you need to create local variables but don't want to use the var statement then there is another way: create additional unused parameters to your function. Ta-da. [mono]function(parm1,parm2,var1,var2){}[/mono], no need for var and the result is (almost) exactly the same. (The "almost" lies in the behavior of the arguments object, but that's neglectible here)

- Chain assignments

Like var, assignments can be chained as well (though not combined...). You can do a=b;c=a; or you can do a=b=c;. The result of the assignment operator (=) is the result of the left hand side after the assignment. As noted, you cannot both chain variable declaration and assignment in the same go. var a=b=5; will give you a local variable a with value 5 and a global variable b with value 5 (so yes, it works, but the other variables will be global). This is fine for js1k though ;)

- Conditional statements

Note that for(;;)x; is shorter than while(1)x;, and should be exactly as fast. You can even use the empty space in the for header to do some initialization and save yourself a semi elsewhere.

- parseInt and +

The + operator is one of the most overloaded operators in Javascript. It does addition, string concatenation and converts its operand to a number (besides that it's part of three other operators). The added bonus of + over parseInt is that + will never return NaN. In cases where parseInt returns NaN, + will simply return 0. However, parseInt will ignore leading whitespace and + will not.

Black ninja

- eval

Eval is commonly accepted as a bad practice. However, it sometimes helps to shorten things using eval. More usages at the scurvy pirate section.

- setTimeout, setInterval

Note that if you supply a string to these methods, their behavior is exactly the same as calling eval when the timer activates.

- with

Like eval, with is bad practice because readers and writers usually don't know what's really going on. However, in light of shortening your code it helps. You only need one with statement to have "direct" access to all of its properties. Think of the savings! And all you need to do is sell your soul ;)

- new X

The parentheses after a new expression are optional if you are not supplying any arguments. Most people don't know this is valid in Javascript. It's particularly handy for new Date; and new Image;.

- No var

As mentioned before, for the compo you can lose the var keyword if you are not going to read from a variable before writing to it. There's no other point! It will create a global variables, but that's okay for your demo :)

- Unicode variable names

Javascript allows unicode names in variable names. Note that they must start with a character in the unicode letter category. Also note that js1k submissions are limited in bytes, not characters. Using a character beyond the 8bit unicode range will automatically count as two bytes. Because that's just the way it is.

- Arrays are objects

If you want an array to cycle through, try using a regular object. You will get the shorter for(.. in ..) syntax for free! For-in does not work on arrays because it will enumerate many other properties.

Scarvy pirate

- Eval replaced string

There's also the possibility of putting your script in a string, replacing repetitive strings with a certain token and replacing those tokens right before passing the value on to eval using .replace and regular expressions. See this sexy example: eval("? t() { alert('hello') };t()".replace(/?/g, 'function')). The example doesn't save bytes, but you can write a script that will for your script.

- Bit shifting

The >> and << operators shift bits left and right. One shift results in a number that's exactly *2 or /2 from the (integer casted) argument. So if you must multiply or divide a number by exactly a power of 2, use shift.

- Reusing code

Of course when writing code golfs you need to reuse code. For example: var n = 0; m = Math.round(x); becomes m = x | n = 0;. Assignments are embedded, the result of an expression can be used to initialize others. Prevent blocks by using the comma. Etc.

Now go use the tricks from above and minify your awesome demo even further! Make room for the features and kick some ass!

Hope it helps you :)