Javascript Optimization
- Contents
- 1. Introduction
-
2. General Code Size Optimization
- 2.1. Use Compound var Statements
- 2.2. Consolidate Multiple Assignments on Single Variable
- 2.3. Daisy Chained Assignments
- 2.4. Avoid Redundant Declarations
- 2.5. Use Ternary Operator For Simple Conditionals
- 2.6. Use Ternary Operators For Complex Conditional Assignments
- 2.7. Use Dot Syntax Whenever Possible
- 2.8. Use > -1 And < 0 For String Matching Conditionals
- 2.9. Avoid Braces For One Statement Blocks
- 2.10. Avoid Object and Array Constructors
- 2.11. Omit "0" Before Decimal Point
- 2.12. Coercing Boolean Results
- 2.13. Delimiter Search, Defaulting to Length
- 2.14. Current Time in Milliseconds
- 2.15. Calculating Time Elapsed
- 2.16. Testing For -1 or 1
- 2.17. Testing For 0 or 1
- 3. Scruncher Specific Code Size Optimization
- 4. Uize Specific Code Size Optimization
- 5. General Code Performance Optimization
1. Introduction
This document discusses tricks and techniques for optimizing JavaScript code for performance and/or code size - some general, some specific to UIZE.
Some of the optimization tips, tricks, and techniques discussed just leverage general principles that can be applied to any code, while other techniques are specified to code scrunched using the JavaScript Scruncher, and yet other techniques are specific to the UIZE JavaScript Framework.
2. General Code Size Optimization
2.1. Use Compound var Statements
When declaring multiple variables in the same section of code, you can reduce code size by using the compound var statement.
INSTEAD OF...
var variable1Name = variable1Value; var variable2Name = variable2Value; var variable3Name = variable3Value; var variable4Name = variable4Value;
USE...
var variable1Name = variable1Value, variable2Name = variable2Value, variable3Name = variable3Value, variable4Name = variable4Value ;
WARNING
When using the compound var statement, be careful about how you use daisy chain assignments (e.g. a = b = c). With daisy chain assignment in a var statement, the var declaration is not applied to "inner" identifiers.
So, for example...
var variable1Name = variable2Name = someValue;
...is not equivalent to...
var variable1Name = someValue, variable2Name = someValue ;
In the first code block, variable2Name
would be assigned as a global variable if it wasn't already defined in an enclosing scope.
2.2. Consolidate Multiple Assignments on Single Variable
For code size as well as performance reasons, you should avoid breaking up long expressions (such as string concatenations) into multiple assignments simply for readability.
There are other ways you can make long statements readable and span multiple lines without incurring the hit of repeated variable referencing and assignment.
INSTEAD OF...
htmlStr = ''; htmlStr += '\n'; htmlStr += '';This is my heading
\n'; htmlStr += 'I am breaking up my HTML string construction into multiple statements '; htmlStr += 'simply because I want my code to look elegant, and I am not aware of '; htmlStr += 'other less costly ways of accomplishing the same effect '; htmlStr += 'by using statements that span multiple lines.'; htmlStr += '
USE...
htmlStr = '\n' + '' ;This is my heading
\n' + 'I am breaking up my HTML string construction into multiple lines ' + 'simply because I want my code to look elegant, and I am not aware of ' + 'other less costly ways of accomplishing the same effect ' + 'by using statements that span multiple lines.' + '
Similarly, if you have a long expression that uses multiple "daisy chained" method calls, you might be tempted to break it up into multiple assignment statements purely for supposed readability reasons. Don't! You can break the statement up so that it spans multiple lines while still keeping it a single statement for performance and code size reasons.
INSTEAD OF...
_htmlAsIs = _html; _htmlAsIs = _htmlAsIs.replace (/\t/g,' '); // turn tabs into three spaces _htmlAsIs = _htmlAsIs.replace (/&/g,'&'); // entitize "&" (to kill entities) _htmlAsIs = _htmlAsIs.replace (//g,'>'); // entitize ">" (it's the right thing) _htmlAsIs = '' + _htmlAsIs + '';
USE...
_htmlAsIs = '' + _html .replace (/\t/g,' ') // turn tabs into three spaces .replace (/&/g,'&') // entitize "&" (to kill entities) .replace (//g,'>') // entitize ">" (it's the right thing) + '' ;
Yes, it's a bit more spread out, but it's actually less code in real bytes, is cleaner and easier to look at, and is more efficient.
2.3. Daisy Chained Assignments
When assigning the same value to multiple different variables or object properties, create a daisy chained assignment expression, provided it doesn't alter the meaning of the code.
INSTEAD OF...
function setRgbColorToGray (_rgbColor,_grayLevel) { _rgbColor.red = _grayLevel; _rgbColor.green = _grayLevel; _rgbColor.blue = _grayLevel; }
USE...
function setRgbColorToGray (_rgbColor,_grayLevel) { _rgbColor.red = _rgbColor.green = _rgbColor.blue = _grayLevel; }
The daisy chained assignment syntax works because the result of an assignment statement is the value being assigned, and because it is possible to use an assignment statement as part of a larger expression. So, you can have an assignment statement on the right hand side of another assignment statement. The value being assigned in the right-most assignment (at the end of the daisy chain) is effectively passed - bucket brigade style - all the way to the left-most assignment.
In the above example, there is a small code size savings from not having two extra occurrences of _grayLevel;
, and there is also a small performance benefit from not accessing the _grayLevel
variable's value two extra times.
WARNING
Daisy chained assignment works for simple type values (such as strings, boolean, numbers) and for objects and functions where the intention is to share a reference across multiple variables or properties. Be careful not to change the meaning of the code by using this syntax.
For example, the code...
myObject1.hash = {}; myObject2.hash = {}; myObject3.hash = {};
...is not equivalent to...
myObject1.hash = myObject2.hash = myObject3.hash = {};
In the fist piece of code, the hash
property of each object will have its own reference to an empty object, whereas - in the second piece of code - the hash
property of all of the objects will reference the same empty object.
2.4. Avoid Redundant Declarations
Don't declare variables or functions that will only be used once.
Often such declarations are more indicative of a thought process and serve no purpose in the code execution. The thought process that leads to the creation of redundant identifiers is useful during the prototyping and development phase but redundant identifiers should be collapsed out when the code is being readied for primetime. It may be useful to capture the intent of those redundant identifiers in inline comments, so that whatever meaning they conveyed that was helping in thinking through an algorithm can still be attached to a segment of code that may form part of a larger expression or block.
2.5. Use Ternary Operator For Simple Conditionals
Cases will arise where what needs to be done conditionally is really very simple - like either calling one function (or method) or another, based upon a condition. You may not even care about the return result from those calls. Now, it may not be obvious to consider using a ternary operator in such cases, but there's really no reason not to. After all, there's no law in JavaScript that says you have to use the result from an expression, so if you merely use the ternary operator for its conditionalizing power, that's perfectly fine.
EXAMPLE 1
So, for example, the following code...
if (someState) { function1 (); } else { function2 (); }
...could be expressed more concisely as...
someState ? function1 () : function2 ();
EXAMPLE 2
In this example, the only conditionalized thing ends up being which instance a particular method should be called on, but it's the same method and it takes all the same parameters.
What would typically be written as...
if (someState) { instanceA.someMethod (param1,param2,param3,param4); } else { instanceB.someMethod (param1,param2,param3,param4); }
...could be expressed more concisely as...
(someState ? instanceA : instanceB).someMethod (param1,param2,param3,param4);
This delivers a nice little code size reduction, which would be more pronounced if the parameters required involved expressions to calculate their values, and where one then would have to either duplicate those expressions (NOT a good idea) or declare some variables to hold the values for both the if
and the else
to use.
EXAMPLE 3
In this example, the only conditionalized thing ends up being which method to call on an instance.
What would typically be written as...
if (someState) { instance.someMethod1 (); } else { instance.someMethod2 (); }
...could be expressed more concisely as...
instance [someState ? 'someMethod1' : 'someMethod2'] ();
As in the previous example, people don't often think to use expressions for conditionalizing the slight logic based deviations in their code. There is an irrational skepticism and disbelief that certain expressions can be used in certain places. JavaScript is a fully dynamic language, and the facilitates that it provides are remarkably consistently supported in contexts where it may traditionally seem "wrong" to use dynamic logic (such as, in this example, dynamically referencing a method using a ternary operator expression and then immediately calling the method).
2.6. Use Ternary Operators For Complex Conditional Assignments
With complex conditional blocks that are merely determining what value to assign to a single variable, consider rolling the conditional block into a single expression using ternary operators.
POORLY OPTIMIZED
if (_bounceWidthRatio) { if (_bounceWidthRatio < 0) _bounceWidthRatio = -1 / _bounceWidthRatio; if (_bounceWidthRatio == 1) _bounceWidthRatio = 1.0001 } else { _bounceWidthRatio = 2; }
WELL OPTIMIZED
_bounceWidthRatio = !_bounceWidthRatio ? 2 : _bounceWidthRatio * _bounceWidthRatio == 1 ? 1.0001 : _bounceWidthRatio < 0 ? -1 / _bounceWidthRatio : _bounceWidthRatio ;
At first glance, it may not look as though the supposedly well optimized code is any smaller than the original, until you look at the difference in size of the scrunched code.
POORLY OPTIMIZED - SCRUNCHED
if(_a){if(_a<0)_a= -1/_a;if(_a==1)_a=1.0001}else{_a=2;}
WELL OPTIMIZED - SCRUNCHED
_a= !_a?2:_a*_a==1?1.0001:_a<0? -1/_a:_a;
As you can see, the if
and else
keywords in the poorly optimized version add up. And, if you're concerned about readability of the ternary operator approach, add a few comments to it - these comments will get stripped out when the code gets scrunched.
2.7. Use Dot Syntax Whenever Possible
In the vast majority of cases it's not necessary (and just takes up unnecessary space) to use the []
syntax when dereferencing a property of an object.
INSTEAD OF...
myObject ['myProperty']
USE...
myObject.myProperty
There is no difference in the behavior between the above two forms. The only times when the square bracket notation is useful is when dereferencing based upon an expression or a variable's value, or if the key is a numerical index into an array, or if the key / property name contains special characters that would make it not a syntactically valid identifier in JavaScript, or if the key / property name is a reserved keyword in JavaScript (such as function
). So, you couldn't do myObject.crazy ass property name!!!
but you could do myObject ['crazy ass property name!!!']
. And, you couldn't do myObject.function
but you could do myObject ['function']
.
People sometimes irrationally feel that they need to use the []
notation in certain contexts, such as when dereferencing into the result of a function call, or when dereferencing a property immediately after dereferncing an array element. This is not the case.
INSTEAD OF...
myFunctionThatReturnsAnObject () ['myProperty']
USE...
myFunctionThatReturnsAnObject ().myProperty
And similarly...
INSTEAD OF...
myArrayOfObjects [2] ['myProperty']
USE...
myArrayOfObjects [2].myProperty
2.8. Use > -1 And < 0 For String Matching Conditionals
When testing for a substring match using the indexOf
and search
methods of JavaScript's String
object, test using > -1
and < 0
, rather than != -1
and == -1
.
Both the indexOf
and search
methods return an integer representing the character position in the string where the specified match was found. If the match was not found, the methods return the value -1
. These methods never return any other negative number besides -1
, so checking on greater than negative one
is always equivalent to checking on not equal to negative one
, and checking on less than zero
is always equivalent to checking on equal to negative one
.
Performance tests show that there is no performance difference between the two types of matches, so using "> -1" and "< 0" is preferable because there are fewer characters in the code for each such match.
INSTEAD OF...
if (myString.indexOf (mySubstr) != -1) { // do stuff }
USE...
if (myString.indexOf (mySubstr) > -1) { // do stuff }
INSTEAD OF...
if (myString.indexOf (mySubstr) == -1) { // do stuff }
USE...
if (myString.indexOf (mySubstr) < 0) { // do stuff }
2.9. Avoid Braces For One Statement Blocks
2.9.1. One Statement Conditional
INSTEAD OF...
if (...) { ...; }
USE...
if (...) ... ;
2.9.2. One Statement Loop
INSTEAD OF...
for (...) { ...; }
USE...
for (...) ... ;
2.9.3. One Statement Functions
INSTEAD OF...
function ... (...) {...;}
USE...
funtcion ... (...) {...}
2.10. Avoid Object and Array Constructors
In the majority of cases it is not necessary (and it is somewhat "old school") to use the Object
and Array
constructors. To create empty objects or arrays, just use the literal syntax (aka JSON).
2.10.1. Object
INSTEAD OF...
var myFreshAndShinyObject = new Object ();
USE...
var myFreshAndShinyObject = {};
2.10.2. Array
INSTEAD OF...
var myFreshAndShinyArray = new Array ();
USE...
var myFreshAndShinyArray = [];
The only time it is useful to use the Array
constructor is in the rare case where you want to initialize the array to have a certain number of elements, as in...
var myArrayWithOneHundredElements = new Array (100);
2.11. Omit "0" Before Decimal Point
For floating point number literals in your code, lose the 0
in front the decimal point. It's unnecessary.
INSTEAD OF...
var _scalingFactor = 0.5;
USE...
var _scalingFactor = .5;
One character you don't need in your code is a zero to prefix a floating point number literal - not even if it's a negative number. The literal -.5
works just as well as -0.5
.
2.12. Coercing Boolean Results
When writing an expression where it is either desired or required to return a boolean result, one can coerce a boolean value by simply using a double not.
The not operator coerces a boolean result. Of course, one doesn't want to return the opposite of the desired result, so an additional not will flip the result into the desired "sign".
INSTEAD OF...
someObject1 && someObject2 && someObject2.length ? true : false
USE...
!!(someObject1 && someObject2 && someObject2.length)
2.13. Delimiter Search, Defaulting to Length
When writing code to scan through a string, from one occurrence of a delimiter to the next, it is a common pattern to set the delimiter position variable to the length of the string (i.e. after the last character of the string) when no more instance of the delimiter is found.
Consider the following snippet of code...
_delimPos = _sourceStr.search (_delimRegExp); if (_delimPos == -1) _delimPos = _sourceStr.length;
Chances are, if you've done a good amount of coding, you've seen that pattern before. The behavior of the search
, indexOf
, and lastIndexOf
methods is to return the value -1
when the search string or regular expression is not found. In such cases, however, one may wish that the default was to return the length of the string being searched. However, the trick below does just that and reduces the code size...
_delimPos = (_sourceStr.search (_delimRegExp) + 1 || _sourceStr.length + 1) - 1;
Here, we are counting on the unique behavior of the ||
boolean operator and the treatment of the value 0
as equivalent to false
in boolean expressions. In order to default to the source string's length in the case of no match, the result of the search
method is shifted by adding 1
, so that -1
becomes 0
. Then, the ||
operator can work its magic to fall through to the second part of the boolean expression, resulting in the source string's length plus 1
. Finally, a subtraction applied to the parenthetical shifts the whole result back, so that the search index isn't messed up for cases where there is a match.
Not only is this a little bit less code, but it is also more efficient in terms of performance. In the previous form, every scan for the next delimiter would involve another access to the _delimPos
variable after its assignment, and a re-assignment in the terminating case. The more efficient form never involves an additional access to the variable, and never involves a re-assignment. The cost for this code size and performance savings is an extra addition and subtraction of 1
- something that is already well optimized in the language and processor.
Keeping in mind this principle of the ||
operator, you will encounter many cases where you can neatly collapse several lines of code into a single line.
2.14. Current Time in Milliseconds
It is useful on occasion to get the current time, as measured in milliseconds from 1970 (aka "Unix time" or "POSIX time"). This can be useful for timing the duration of operations, or when constructing uniquifying values to defeat cache or for other purposes.
INSTEAD OF...
var timeInMilliseconds = new Date ().getTime ();
USE...
var timeInMilliseconds = +new Date;
The second, more concise form works because 1) it is not necessary to append empty parentheses when an object constructor takes no arguments, and 2) the prepended "+" operator coerces the newly created Date
object to an integer by invoking its valueOf
intrinsic method (which returns the same value as the getTime
method).
2.15. Calculating Time Elapsed
There are times when you want to calculate the duration of an operation. A simple and concise pattern for doing this is as follows...
var before = new Date; /* do a whole bunch of intensive stuff that takes a crapload of time */ var duration = new Date - before;
This works because 1) it is not necessary to append empty parentheses when an object constructor takes no arguments, and 2) subtracting one Date
object from another coerces both Date
objects to integer values by invoking their valueOf
intrinsic method (which returns the same value as the getTime
method).
2.16. Testing For -1 or 1
To test if a number is either -1
or 1
, you can take advantage of the fact that 1
squared and -1
squared both produce the result 1
.
INSTEAD OF
if (number == -1 || number == 1) { // do stuff }
USE...
if (number * number == 1) { // do stuff }
2.17. Testing For 0 or 1
To test if a number is either 0
or 1
, you can take advantage of the fact that double logical notting of both 0
and 1
will produce a value that will test as equivalent to the original number.
INSTEAD OF
if (number == 0 || number == 1) { // do stuff }
USE...
if (number == !!number) { // do stuff }
For the value 0
, double logical notting produces the value false
, which tests as equivalent to 0
. Similarly, double logical notting of the value 1
produces the value true
, which tests as equivalent to 1
. However, double logical notting of 1.1
also produces the value true
, but this does not test as equivalent to 1.1
.
3. Scruncher Specific Code Size Optimization
3.1. Capture References To Static Methods
Capture references to frequently used static methods of classes or JavaScript's built-in objects, using a scrunchable local scope variable.
There are many static methods of classes that are really just static properties that happen to be function references. In other words, they don't really need to be called on the class or object. For these types of statics, you can capture a reference to them in a local scope variable with a scrunchable identifier name. For frequently used statis methods, this will reduce the scrunched code size and will also offer the benefit of a slight performance improvement, since repeated use of the statics will not incur repeated dereferencing of the static's host object.
EXAMPLE
var _Math_round = Math.round; function _roundArray (_elements) { for (var _elementNo = _elements.length - 1, _elementNo >= 0, _elementNo--) { _elements [_elementNo] = _Math_round (_elements [_elementNo]); } } function _setNodeCoordsInPixels (_nodeRef,_left,_top,_width,_height) { var _nodeStyle = _nodeRef.style; _nodeStyle.left = _Math_round (_left) + 'px'; _nodeStyle.top = _Math_round (_top) + 'px'; _nodeStyle.width = _Math_round (_width) + 'px'; _nodeStyle.height = _Math_round (_height) + 'px'; }
In the above example, a reference to the Math.round
static method is kept in the _Math_round
private local scope variable. When the code is scrunched, the variable might be named something like b_1
. In this example, two functions get the benefit of this captured reference. The _roundArray
function benefits from improved performance during its iteration through an array, since there's not repeated dereferencing into the Math
object. The _setNodeCoordsInPixels
function gets a code size benefit, since four _Math_round
scrunched down will be fewer characters than four Math.round
.
This same rule can apply to any static methods that are defined as part of the interfaces for various classes that you may write or that may be part of the UIZE JavaScript Framework. Now, there might be some static methods that do require being called on their class, so make sure your code works when you use this technique.
4. Uize Specific Code Size Optimization
4.1. The valueOf Shortcut
The Uize.Class
base class implements a valueOf
instance method that will be invoked automatically by JavaScript whenever an instance of a Uize.Class
subclass is involved in an expression.
This provides a shortcut to accessing the value
state property for instances of classes that declare this property. In such cases, you can use an instance reference in an expression - such as a mathematical expression, or a string concatenation - as though the object reference were a proxy for its value.
4.1.1. Coercion to Number
If you involve an instance of a Uize.Class
subclass in a math expression, the valueOf
intrinsic method will be invoked and the value of your instance will be converted to a number.
INSTEAD OF...
markupPercentSlider.get ('value') / 100
USE...
markupPercentSlider / 100
To coerce an instance's value to a number type without changing its value, simply prepend a "+" character. So...
INSTEAD OF...
markupPercentSlider.get ('value')
USE...
+markupPercentSlider
4.1.2. Coercion to String
If you involve an instance of a Uize.Class
subclass in a string expression, the valueOf
intrinsic method will be invoked and the value of your instance will be converted to a string.
INSTEAD OF...
borderThicknessSlider.get ('value') + 'px'
USE...
borderThicknessSlider + 'px'
To coerce an instance's value to a string type without changing its value, simply append an empty string. So...
INSTEAD OF...
textInputWidget.get ('value')
EXAMPLE
textInputWidget + ''
5. General Code Performance Optimization
5.1. The Reverse Loop Trick
There are times when it is possible to loop backwards in order to save a bit of processor time, while still producing the same outcome.
TYPICAL APPROACH
function getTotal (_elements) { var _total = 0; for (var _elementNo = 0; _elementNo < _elements.length; _elementNo++) _total += _elements [_elementNo] ; return _total; }
In the above example, the getTotal
function calculates the sum of all the values in the specified array of numbers. It's not a complicated algorithm, and one's first instinct might be to just write a forward loop to iterate through the elements of the array. Of course, this means that the loop terminator test has to check that the _elementNo
variable doesn't go beyond the last element, and this check involves dereferencing the length
property of the _elements
variable on each iteration.
Now, in this algorithm (as with other algorithms you may encounter that involve iterating through an array), the order in which you run through the array really doesn't matter. In this case, for instance, the sum of all the values will be the same, regardless of loop direction. So, one can save the cost of checking length
on each iteration by starting from the end and iterating towards element 0. This makes the loop terminator test a simple comparison against a number literal. This type of optimization will naturally benefit longer loops more than shorter ones.
BETTER PERFORMANCE
function getAverage (_elements) { var _total = 0; for (var _elementNo = _elements.length; --_elementNo >= 0;) _total += _elements [_elementNo] ; return _total; }
We're also doing another trick in the optimized version above. By starting the loop with _elementNo
initialized to _elements.length
, we can avoid the subtraction of 1
. Now, naturally we don't actually want that to be the first element that we encounter, so we can prefix decrement the _elementNo
variable right in our loop-test expression. And, since we're decrementing in the loop-test, then we don't need a counting expression (still need the semi-colon, though, or we'll get a syntax error for an invalid format for
loop).