Efficient JavaScript
ECMAScript
Avoid using eval or the Function constructor
Each time eval or the Function constructor is called on a string representing source code, the script engine must start the machinery that converts the source code to executable code. This is usually expensive for performance - easily a hundred times more expensive than a simple function call, for example.
The eval function is especially bad, as the contents of the string passed to eval cannot be known in advance. Since the code is interpreted in the context of the call to eval this means that the compiler cannot optimise the surrounding context, and the browser is left to interpret much of the surrounding code at runtime. This adds an additional performance impact.
The Function constructor is not quite as bad as eval, since using it does not affect the code surrounding the use, but it can still be quite slow.
Rewrite that eval
eval is not only inefficient, it is almost always unnecessary. In many cases, it is used because information has been provided as a string, and it is assumed that eval is the way to use that information. The following example shows a common mistake:
function getProperty(oString) {
var oReference;
eval('oReference = test.prop.'+oString);
return oReference;
}
This code performs exactly the same function, but avoids using eval:
function getProperty(oString) {
return test.prop[oString];
}
The code that does not use eval performs around 95% faster than the original in Opera 9, Firefox, and Internet Explorer, and around 85% faster in Safari. (Note that this does not include the time needed to call the function itself.)
If you want a function, use a function
This example shows a common use for the Function constructor:
function addMethod(oObject,oProperty,oFunctionCode) {
oObject[oProperty] = new Function(oFunctionCode);
}
addMethod(myObject,'rotateBy90','this.angle=(this.angle+90)%360');
addMethod(myObject,'rotateBy60','this.angle=(this.angle+60)%360');
This code provides the same functionality, but avoids using the Function constructor. This is done by creating an anonymous function instead, which can be referenced just like any other object:
function addMethod(oObject,oProperty,oFunction) {
oObject[oProperty] = oFunction;
}
addMethod(myObject,'rotateBy90',function () { this.angle=(this.angle+90)%360; });
addMethod(myObject,'rotateBy60',function () { this.angle=(this.angle+60)%360; });
Avoid using with
Although often seen as a convenience for the developer, with can be expensive for performance. The with construct introduces an extra scope for the script engine to search through whenever a variable is referenced. This alone produces a minor performance decrease. However, the contents of that scope are not known at compile time, meaning that the compiler cannot optimize for it, in the same way as it can with normal scopes (such as those created by functions).
A more efficient approach that also presents a convenience for the developer, is to reference the object using a normal variable, and then access the properties from that instead. This will obviously only work if the properties are not literal types, such as a string or boolean.
Consider this code:
with( test.information.settings.files ) {
primary = 'names';
secondary = 'roles';
tertiary = 'references';
}
This will be more efficient for the script engine:
var testObject = test.information.settings.files;
testObject.primary = 'names';
testObject.secondary = 'roles';
testObject.tertiary = 'references';
Don't use try-catch-finally inside performance-critical functions
The try-catch-finally construct is fairly unique. Unlike other constructs, it creates a new variable in the current scope at runtime. This happens each time the catch clause is executed, where the caught exception object is assigned to a variable. This variable does not exist inside other parts of the script even inside the same scope. It is created at the start of the catch clause, then destroyed at the end of it.
Because this variable is created and destroyed at runtime, and represents a special case in the language, some browsers do not handle it very efficiently, and placing a catch handler inside a performance critical loop may cause performance problems when exceptions are caught.
If possible, exception handling should be done at a higher level in the script where it may not occur so frequently, or avoided by checking if the desired action is allowed first. The following example shows a loop that may throw several exceptions, if the desired properties do not exist:
var oProperties = ['first','second','third',...,'nth'], i;
for( i = 0; i < oProperties.length; i++ ) {
try {
test[oProperties].someproperty = somevalue;
} catch(e) {
...
}
}
In many cases, the try-catch-finally construct could be moved so that it surrounds the loop. This does change the semantics a little, since if an exception is thrown, the loop will be halted, although code following it will continue to run:
var oProperties = ['first','second','third',...,'nth'], i;
try {
for( i = 0; i < oProperties.length; i++ ) {
test[oProperties].someproperty = somevalue;
}
} catch(e) {
...
}
In some cases, the try-catch-finally construct could be avoided completely by checking for properties, or using another appropriate test:
var oProperties = ['first','second','third',...,'nth'], i;
for( i = 0; i < oProperties.length; i++ ) {
if( test[oProperties] ) {
test[oProperties].someproperty = somevalue;
}
}
Isolate uses of eval and with
Since these constructs can impact performance so significantly, their use should be kept to as little as possible, but there are some times when you may need to use them. If a function is repeatedly called, or a loop is repeatedly evaluated, then it is best to avoid these constructs within it. They are best suited to code that is executed only once, or only a few times, and not within performance critical code.
Wherever possible, isolate them from other code, so that they do not affect its performance. This could mean, for example, putting them inside a top-level function, or running them once, and storing their result, so you can use the result again later without having to rerun the code.
Although not so important, the try-catch-finally construct can have performance impacts in some browsers, including Opera, so you may also wish to isolate that in the same way.
Avoid using global variables
It can be tempting to create variables in the global scope, simply because that is easy to do. However, there are several reasons why this can make scripts run more slowly.
To begin with, if code inside a function or another scope references that variable, the script engine has to step up through each scope in turn until it reaches the global scope. A variable in the local scope will be found more quickly.
Variables in the global scope also persist through the life of the script. In the local scope, they are destroyed when the local scope is lost. The memory they use can then be freed by the garbage collector.
Lastly, the global scope is shared by the window object, meaning that it is in essence two scopes, not just one. In the global scope, variables are always located using their name, instead of using an optimized predefined index, as they can be in local scopes. A global variable will take longer for the script engine to find, as a result.
Functions are also usually created in the global scope. This means that functions that call other functions, that in turn call other functions, increase the number of times the script engine has to step back to the global scope to locate them.
Take this simple example, where i and s are in the global scope, and the function uses those global variables:
var i, s = '';
function testfunction() {
for( i = 0; i < 20; i++ ) {
s += i;
}
}
testfunction();
This alternative version performs measurably faster. In most current browsers, including Opera 9, and the latest versions of Internet Explorer, Firefox, Konqueror and Safari, execution is about 30% faster than the original.
function testfunction() {
var i, s = '';
for( i = 0; i < 20; i++ ) {
s += i;
}
}
testfunction();
Beware of implicit object conversion
Literals, such as strings, numbers, and boolean values, have two representations within ECMAScript. Each of them can be created as either a value or an object. For example, a string value is created simply by saying var oString = 'some content';, while an equivalent string object is created by saying var oString = new String('some content');.
Any properties and methods are defined on the string object, not the value. When you reference a property or method of a string value, the ECMAScript engine must implicitly create a new string object with the same value as your string, before running the method. This object is only used for that one request, and will be recreated next time you attempt to use a method of the string value.
This example requires the script engine to create 21 new string objects, once for each time the length property is accessed, and once each time the charAt method is called:
var s = '0123456789';
for( var i = 0; i < s.length; i++ ) {
s.charAt(i);
}
This equivalent example creates just a single object, and will perform better as a result:
var s = new String('0123456789');
for( var i = 0; i < s.length; i++ ) {
s.charAt(i);
}
If your code calls methods of literal values very often, you should consider converting them into objects instead, as in the previous example.
Note that although most of the points in this article are relevant to all browsers, this particular optimization is aimed mainly at Opera. It may also affect some other browsers, but can be a little slower in Internet Explorer and Firefox.
Avoid for-in in performance-critical functions
The for-in loop has its place, but is often misused, when a normal for loop would be more appropriate. The for-in loop requires the script engine to build a list of all the enumerable properties, and check for duplicates in that list, before it can start the enumeration.
Very often, the script itself already knows what properties must be enumerated. In many cases, a simple for loop could be used to step through those properties, especially if they are named using sequential numbers, such as with an array, or an object that is given properties to make it appear to be an array (an example would be a NodeList object created by DOM).
This is an example of incorrect use of a for-in loop:
var oSum = 0;
for( var i in oArray ) {
oSum += oArray;
}
A for loop would be more efficient:
var oSum = 0;
var oLength = oArray.length;
for( var i = 0; i < oLength; i++ ) {
oSum += oArray;
}
Use strings accumulator-style
String concatenation can be an expensive process. Using the + operator does not wait for the result to be assigned to a variable. Instead, it creates a new string in memory, assigns its result to that string, and it is that new string that may be assigned to a variable. The following code shows a common assignment of a concatenated string:
a += 'x' + 'y';
That code would be evaluated by firstly creating a temporary string in memory, assigning the concatenated value of 'xy', then concatenating that with the current value of a, and finally assigning the resulting value of that to a. The following code uses two separate commands, but because it assigns directly to a each time, the temporary string is not used. The resulting code is around 20% faster in many current browsers, and potentially requires less memory, as it does not need to temporarily store the concatenated string:
a += 'x';
a += 'y';
Primitive operations can be faster than function calls
Although not significant in normal code, there is a potential for improved speed in performance critical loops and functions, by replacing function calls with an equivalent primitive operation. An example would be the push method of an array, that is slower than simply adding an item to the index at end of the array. Another example would be methods of the Math object, where in most cases, simple mathematical operators would be more appropriate.
var min = Math.min(a,b);
A.push(v);
These alternatives provide the same functionality, while performing better:
var min = a < b ? a : b;
A[A.length] = v;
Pass functions, not strings, to setTimeout() and setInterval()
The setTimeout() and setInterval() methods are very closely related to eval. If they are passed a string, then after the specified delay, that string will be evaluated in exactly the same way as with eval, including the associated performance impact.
These methods can, however, accept a function as the first parameter, instead of a string. This function will be run after the same delay, but can be interpreted and optimized during compilation, with improved performance as a result. Typical examples of using strings as the parameter would be these:
setInterval('updateResults()',1000);
setTimeout('x+=3;prepareResult();if(!hasCancelled){runmore();}',500);
In the first case, the function can simply be referenced directly. In the second case, an anonymous function can be wrapped around the code:
setInterval(updateResults,1000);
setTimeout(function () {
x += 3;
prepareResult();
if( !hasCancelled ) {
runmore();
}
},500);
Note that in all cases, the timeout or interval delay may not be honoured exactly. In general, browsers will take a little longer than the requested delay. Some may compensate for that with intervals by firing the next one slightly early instead. Others will simply try to wait for the correct amount of time every time. Factors such as CPU speed, thread states, and JavaScript load will affect the accuracy of the delay. Most browsers will be unable to give a delay of 0 ms, and may impose a minimum delay, typically between 10 and 100 ms.