Working with strings
String concatenation causes major problems with Internet Explorer 6 and 7 garbage collection performance. Although these issues have been addressed in Internet Explorer 8 — concatenating is actually slightly more efficient on IE8 and other non-IE browsers such as Chrome — if a significant portion of your user population uses Internet Explorer 6 or 7, you should pay serious attention to the way you build your strings.
Consider this example:
var veryLongMessage =
'This is a long string that due to our strict line length limit of' +
maxCharsPerLine +
' characters per line must be wrapped. ' +
percentWhoDislike +
'% of engineers dislike this rule. The line length limit is for ' +
' style purposes, but we don't want it to have a performance impact.' +
' So the question is how should we do the wrapping?';
Instead of concatenation, try using a join:
var veryLongMessage =
['This is a long string that due to our strict line length limit of',
maxCharsPerLine,
' characters per line must be wrapped. ',
percentWhoDislike,
'% of engineers dislike this rule. The line length limit is for ',
' style purposes, but we don't want it to have a performance impact.',
' So the question is how should we do the wrapping?'
].join();
Similarly, building up a string across conditional statements and/or loops by using concatenation can be very inefficient. The wrong way:
var fibonacciStr = 'First 20 Fibonacci Numbers
';
for (var i = 0; i < 20; i++) {
fibonacciStr += i + ' = ' + fibonacci(i) + '
';
}
The right way:
var strBuilder = ['First 20 fibonacci numbers:'];
for (var i = 0; i < 20; i++) {
strBuilder.push(i, ' = ', fibonacci(i));
}
var fibonacciStr = strBuilder.join('');
Building strings with portions coming from helper functions
Build up long strings by passing string builders (either an array or a helper class) into functions, to avoid temporary result strings.
For example, assuming
buildMenuItemHtml_
needs to build up a string from literals and variables and would use a string builder internally, instead of using:var strBuilder = [];
for (var i = 0, length = menuItems.length; i < length; i++) {
strBuilder.push(this.buildMenuItemHtml_(menuItems[i]));
}
var menuHtml = strBuilder.join();Use:var strBuilder = [];
for (var i = 0, length = menuItems.length; i < length; i++) {
this.buildMenuItem_(menuItems[i], strBuilder);
}
var menuHtml = strBuilder.join();
Defining class methods
The following is inefficient, as each time a instance of
baz.Bar
is constructed, a new function and closure is created for foo:baz.Bar = function() {
// constructor body
this.foo = function() {
// method body
};
}The preferred approach is:baz.Bar = function() {
// constructor body
};baz.Bar.prototype.foo = function() {
// method body
};
With this approach, no matter how many instances of
baz.Bar
are constructed, only a single function is ever created for foo
, and no closures are created.Initializing instance variables
Place instance variable declaration/initialization on the prototype for instance variables with value type (rather than reference type) initialization values (i.e. values of type number, Boolean, null, undefined, or string). This avoids unnecessarily running the initialization code each time the constructor is called. (This can’t be done for instance variables whose initial value is dependent on arguments to the constructor, or some other state at time of construction.)
For example, instead of:
foo.Bar = function() {
this.prop1_ = 4;
this.prop2_ = true;
this.prop3_ = [];
this.prop4_ = 'blah';
};
Use:
foo.Bar = function() {
this.prop3_ = [];
};foo.Bar.prototype.prop1_ = 4;foo.Bar.prototype.prop2_ = true;foo.Bar.prototype.prop4_ = ‘blah’;
Avoiding pitfalls with closures
Closures are a powerful and useful feature of JavaScript; however, they have several drawbacks, including:
- They are the most common source of memory leaks.
- Creating a closure is significantly slower then creating an inner function without a closure, and much slower than reusing a static function. For example:
function setupAlertTimeout() {
is slower than:
var msg = 'Message to alert';
window.setTimeout(function() { alert(msg); }, 100);
}function setupAlertTimeout() {
which is slower than:
window.setTimeout(function() {
var msg = 'Message to alert';
alert(msg);
}, 100);
}function alertMsg() {
var msg = 'Message to alert';
alert(msg);
}function setupAlertTimeout() {
window.setTimeout(alertMsg, 100);
}- They add a level to the scope chain. When the browser resolves properties, each level of the scope chain must be checked. In the following example:
var a = 'a';
function createFunctionWithClosure() {
var b = ‘b’;
return function () {
var c = ‘c’;
a;
b;
c;
};
}var f = createFunctionWithClosure();
f();whenf
is invoked, referencinga
is slower than referencingb
, which is slower than referencingc
.
See IE+JScript Performance Recommendations Part 3: JavaScript Code inefficiencies for information on when to use closures with IE.
Avoiding with
Avoid using
with
in your code. It has a negative impact on performance, as it modifies the scope chain, making it more expensive to look up variables in other scopes.Avoiding browser memory leaks
Memory leaks are an all too common problem with web applications, and can result in huge performance hits. As the memory usage of the browser grows, your web application, along with the rest of the user’s system, slows down. The most common memory leaks for web applications involve circular references between the JavaScript script engine and the browsers’ C++ objects’ implementing the DOM (e.g. between the JavaScript script engine and Internet Explorer’s COM infrastructure, or between the JavaScript engine and Firefox XPCOM infrastructure).
Here are some rules of thumb for avoiding memory leaks:
Use an event system for attaching event handlers
The most common circular reference pattern [ DOM element --> event handler --> closure scope --> DOM ] element is discussed in this MSDN blog post. To avoid this problem, use one of the well-tested event systems for attaching event handlers, such as those in Google doctype, Dojo, or JQuery.
In addition, using inline event handlers can lead to another kind of leak in IE. This is not the common circular reference type leak, but rather a leak of an internal temporary anonymous script object. For details, see the section on “DOM Insertion Order Leak Model” in Understanding and Solving Internet Explorer Leak Patterns and and an example in this JavaScript Kit tutorial.
Avoid expando properties
Expando properties are arbitrary JavaScript properties on DOM elements and are a common source of circular references. You can use expando properties without introducing memory leaks, but it is pretty easy to introduce one by accident. The leak pattern here is [ DOM element --> via expando--> intermediary object --> DOM element ]. The best thing to do is to just avoid using them. If you do use them, only use values with primitive types. If you do use non-primitive values, nullify the expando property when it is no longer needed. See the section on “Circular References” in Understanding and Solving Internet Explorer Leak Patterns.
How to Optimizing JavaScript code
Reviewed by JohnBlogger
on
6:30 PM
Rating:
No comments: