Dev.Opera - Follow the standards, break the rulesDev.Opera - Follow the standards, break the rules

Login

Lost password?

Forums » Article Discussions

Discuss the articles posted at DevOpera

Note: You need to login to post in the forums. if you don't have an account you first need to sign up.

By tarquinwj O anchor Thursday, 2. November 2006, 16:19:29

Efficient JavaScript

As Web pages become more like applications, the performance of scripts is having a bigger effect. Learn how to make your Web applications more efficient through a series of simple changes.

( Read the article )

By robodesign anchor Thursday, 2. November 2006, 18:11:04

avatarGreat article. It's precisely what I was interested into reading.

I have several questions/suggestions.

In "Avoid using with":
var testObject = test.information.settings;
testObject.files.primary = 'names';

Could be rewritten as:
var testObject = test.information.settings.files;
testObject.primary = 'names';
...

If I am not mistaken :smile:.

Also, regarding loops. I optimize my scripts using:
var i, n = arr.length;
for( i = 0; i < n; i++ ) {
  ...
}

instead of:
for( var i = 0; i < arr.length; i++ ) {
  ...
}

Is this true optimization or not? Checking for the length might be more time consuming.

By tarquinwj O anchor Thursday, 2. November 2006, 18:34:37

avatar

Originally posted by robodesign:

In "Avoid using with":
var testObject = test.information.settings;
testObject.files.primary = 'names';
Could be rewritten as:
var testObject = test.information.settings.files;
testObject.primary = 'names';


Yep. I was supposed to change that earlier, but got confused by a typo :wink:
Thanks for the reminder. Fixed now.

Originally posted by robodesign:

I optimize my scripts using:
var i, n = arr.length;
for( i = 0; i < n; i++ ) {
...
}
instead of:
for( var i = 0; i < arr.length; i++ ) {
...
}
Is this true optimization or not?


This depends very much on what you do inside the loop.
In a basic loop, there is no real difference, since the engine should be able to cache the length for you anyway.

If you change the array inside the loop, it will make a difference, since the optimisations may not be possible - especially if you change the length. How much of a difference depends on what you are doing. Changing the contents of existing cells has a very small influence only, your optimised loop is perhaps just a few percent faster at most (2% in my test in Opera). Changing the length will make a much bigger difference, but your caching will fail to work with the updated length anyway, so it would probably not be a useful optimisation in that case.

If the array is not a real array, such as a DOM NodeList, and you modify the DOM inside the loop, it makes a very big difference, and your optimised loop is much better. This point is covered in the DOM chapter:
http://dev.opera.com/articles/view/48/?page=3#traversemodify
http://dev.opera.com/articles/view/48/?page=3#traversemodify

By robodesign anchor Thursday, 2. November 2006, 18:38:44

avatarThanks for the reply. The DOM NodeList is the most important one, as I expected.

By Kildor anchor Thursday, 2. November 2006, 18:49:58

avatarhttp://dev.opera.com/articles/view/48/?page=3#stylechanges
Why do not use
posElem.style='rules'

It work in all current browsers, or not?
Again Open standarts versus real world?

By tarquinwj O anchor Thursday, 2. November 2006, 20:54:26

avatar

Originally posted by Kildor:

Why do not use
posElem.style='rules'


To my knowledge there is no requirement for a browser to allow this. The DOM 2 CSS spec specifically says it should be read-only; 'readonly attribute CSSStyleDeclaration style;' - although this may change in future versions.

Opera and Safari/Konqueror allow you to write to it anyway. Firefox and iCab do not (Firefox throws an error).

I prefer to use the standard approach wherever possible. In addition, this part of the code is used if the browser does not support cssText - which iCab does not. Since I aim to get maximum compatibility, I will use the standard approach, as that does work in iCab, while the one you suggested does not. (NetFront and ICE are the same.)

By mr7clay anchor Saturday, 4. November 2006, 03:22:50

avatarRe: implicit object conversion, this is a real eye-opener. I guess the same is true for array and object literals like [] and {}... If you're going to call length or an Array method even once, you might as well get it over with and define the variable with "new".

It's nice to see the focus on generally vendor-neutral performance wins. I think too many go into topics like loop unrolling, trying to outsmart the compiler and I wonder if these end up preventing better native optimizations.

By johnnysaucepn anchor Saturday, 4. November 2006, 03:37:04

avatar

Originally posted by mr7clay:

I wonder if these end up preventing better native optimizations.

They probably prevent future native optimisations as well.

By lth anchor Saturday, 4. November 2006, 13:16:38

avatarA gentle correction to Tarquin's post: consider the loop

for ( i=0 ; i < a.length ; i++ )
...

the compiler or execution engine can't really precompute and save the value of a.length to avoid recomputation at each iteration. It can only do so when the contents of the body can't affect the value of that expression. With the current language there are limited opportunities for that; with getters and setters in the language the opportunities will be even fewer.

And as for what mr7clay writes, the expressions [...] and {...} are not array and object literals, but array and object initializers. The expression [1,2,3] is for all practical purposes the same as

tmp = new Array();
tmp[0]=1;
tmp[1]=2;
tmp[2]=3;
tmp

The same for object initializers.

--lars, primary ECMAScript engine maintainer at Opera

By tarquinwj O anchor Saturday, 4. November 2006, 13:44:08

avatar

Originally posted by mr7clay:

Re: implicit object conversion, this is a real eye-opener. I guess the same is true for array and object literals like [] and {}


No. With arrays and objects, the short form and the object constructor are equivalent, since an array and object do not have a primitive value. They are not literals, and are always an object. I am not so sure about what category RegExp fall into, but I think they are also always objects - their performance seems to agree.

You can try it this way:

alert( typeof( 'foo' ) ); // -> string
alert( typeof( new String('foo') ) ); // -> object

alert( typeof( [] ) ); // -> object
alert( typeof( new Array() ) ); // -> object

By tarquinwj O anchor Saturday, 4. November 2006, 13:48:22

avatar

Originally posted by lth:

A gentle correction to Tarquin's post: consider the loop


heh, yes, sorry, I was trying to correct myself, but I am unable to edit posts here - teething troubles with the forum it seems :smile:

My rewording would have been: -

Imagine that you change the array inside the loop. It will make a difference, since the engine will not be able to cache the length for you. In fact, since it is difficult to know in advance if your code will change the length (since you may be referencing the array via any number of different ways), caching the length may not be easily possible anyway. So your optimisation will make a difference.

How much of a difference depends on what you are doing. In a basic loop, it has a very small influence only, your optimised loop is perhaps just a few percent faster at most (2% in my test in Opera). Changing the length inside the loop may make a bigger difference in some browsers, but your caching will fail to work with the updated length anyway, so it would probably not be a useful optimisation in that case.

-

I also got the second link in that comment wrong - should have been:
http://dev.opera.com/articles/view/48/?page=3#cachevalues

By MarkSchenk anchor Monday, 6. November 2006, 06:47:18

avatarThanks for the article Tarquinwj, I'll be taking these aspects into account in my future coding projects.

About measuring JavaScript performance: do you happen to have any experience with (free) JavaScript profilers, that might help to isolate performance bottlenecks?

I'm working on a javascript project that's slow as molasses in Firefox, but flies in Opera, and before I completely attribute this to Opera's faster engine, I want to see if there is something I could optimize. I tried downloading Venkman, but my bright and shiny FF2 refuses to connect to the addon servers... so much for extensibility...

By tarquinwj O anchor Monday, 6. November 2006, 09:17:52

avatar

Originally posted by MarkSchenk:

About measuring JavaScript performance: do you happen to have any experience with (free) JavaScript profilers, that might help to isolate performance bottlenecks?


Yes. There is a fairly simplistic free one. It's called 'new Date()'. It's resolution is a little low, typically 10ms is your minimum. But if you have something running exceptionally slow, then it may just be enough for you to see it.

<pre><code>var recordTimes = [];
function noteTime(oName) {
recordTimes[recordTimes.length] = [new Date(),oName];
}
function showTimings() {
if( !recordTimes.length ) { return; }
var lastDate = recordTimes[0][0];
for( var n = 0, s = ''; n < recordTimes.length; n++ ) {
s += '<tr><td>'+recordTimes[n][1]+'<\/td><td>'+(recordTimes[n][0].getTime()-lastDate.getTime())+'<\/td><\/tr>';
lastDate = recordTimes[n][0];
}
var foo = window.open('','_blank');
foo.document.write('<table>'+s+'<\/table>');
foo.document.close();
}</code></pre>

Then wherever you want to log the time, use:

noteTime('some name');

And to see the result, use this bookmarklet:

javascript:showTimings();

It will show you the intervals between times that you took notes. If you see some that are sufficiently long, then that will probably be the cause of the slowdown.

Originally posted by MarkSchenk:

I tried downloading Venkman, but my bright and shiny FF2 refuses to connect to the addon servers.


Managed to download it without any problem (searched for it on my favourite search engine, and downloaded it from the project site). Don't know how to use it, though - it just gave me a load of useless information about the scripts in its own chrome (even though the option to ignore them was ticked), and nothing about my script :smile: I am sure this is just because I have not used it before though.

But if you still have trouble, I am afraid I have to point you to the owners of the project, since I do not have enough experience with it.

By MarkSchenk anchor Monday, 6. November 2006, 14:31:00

avatar

Originally posted by tarquinwj:


Yes. There is a fairly simplistic free one. It's called 'new Date()'.


:-)

Thanks for the code. I should have thought of this... it's like the tic/toc functions in Matlab. With some adapting it might actually be able to do what I want; the problem most likely lies in a function called during a loop, so I'll have to sum all the time spent in that called function.

Originally posted by tarquinwj:

Managed to download it without any problem (searched for it on my favourite search engine, and downloaded it from the project site). Don't know how to use it, though - it just gave me a load of useless information about the scripts in its own chrome (even though the option to ignore them was ticked), and nothing about my script :smile:


Same here, I downloaded the extension in my favourite browser, but couldn't get the extension to do anything useful; just debug problems for its chrome.

By robodesign anchor Monday, 6. November 2006, 19:15:13

avatar

Originally posted by MarkSchenk:

Same here, I downloaded the extension in my favourite browser, but couldn't get the extension to do anything useful; just debug problems for its chrome.
Last time I used Venkman, it was "OK". It helped me. However, that's a LONG time ago (and it was with the Mozilla Suite, not with Firefox). I never had Venkman in Firefox. I remember I didn't like it very much for being overly complex.

I do recommend you to try Firebug. It's a Firefox extension which provides many very good features, including a working debugger that I found easy and nice to use.

By tarquinwj O anchor Monday, 6. November 2006, 19:38:27

avatar

Originally posted by robodesign:

I do recommend you to try Firebug. It's a Firefox extension which provides many very good features, including a working debugger that I found easy and nice to use.


It does look a lot more comfortable than the other one, but no profiling that I could see. So I doubt this would be useful given the problem that Mark was trying to debug, unfortunately. Or did I miss it?

By robodesign anchor Monday, 6. November 2006, 20:28:11

avatar

Originally posted by tarquinwj:

It does look a lot more comfortable than the other one, but no profiling that I could see. So I doubt this would be useful given the problem that Mark was trying to debug, unfortunately. Or did I miss it?
Yes, no profiling (AFAIK). Firebug is a good replacement for Venkman, as a JS debugger.

By krang anchor Tuesday, 7. November 2006, 10:17:51

avatarHi tarquinwj, thanks for a great article!

Just wanted to know, if on...

http://dev.opera.com/articles/view/48/?page=3#traversemodify

Would it have been possible to do...

----------------------
var allPara = document.getElementsByTagName('p');
for (var i = (allPara.length - 1); i >= 0 ; i--) {
allPara[ i ].appendChild(document.createTextNode(i));
}
----------------------

This does take some head scratching as the array is processed in reverse, but it means that the "length" is only used once.

However there are times when this does not work... in which case a "var allParaLen = allPara.length" before the "for" loop can help.

Not sure if I have missed the point though... other than that slight change, a great article!

Thanks again,
Craig Francis

By crisp anchor Wednesday, 8. November 2006, 10:24:17

avatarThis article should come with a notice "beware of premature optimization and browser-specific implementations" :wink:

I was fascinated by the 'implicit object conversion' item and decided to do some tests with it in various browsers. For one I noticed that there was not a notable difference in performance in Opera itself, but there was a +/- 10% difference in other browsers (IE7 and Firefox 2.0) where the 'improved' example was actually slower. A far better optimization would be to rewrite the for-loop since that's where most of the time is spent - evaluating the length property with every iteration. This is a many times faster in Firefox:

var s = '0123456789';
for( var i = 0, l = s.length; i < l; i++ )
{
s.charAt(i);
}

but then again a.m. example is actually slower in Opera - it all boils down to the implementation and my advice would be to test each and every optimization in various browsers to find out what actually gives the best result in all of them.

By tarquinwj O anchor Thursday, 9. November 2006, 10:52:11

avatar

Originally posted by krang:

Would it have been possible to do...
for (var i = (allPara.length - 1); i >= 0 ; i--) {


Yes.

By xErath anchor Tuesday, 14. November 2006, 03:48:18

avatarThere's an error in section Making several style changes at once
if( typeof( posElem.style.cssText != 'undefined' ) ) {

Should be
if( typeof( posElem.style.cssText ) != 'undefined' ) {


By tarquinwj O anchor Tuesday, 14. November 2006, 15:01:16

avatar

Originally posted by xErath:

There's an error in section Making several style changes at once


Thankyou. Fixed.

I also added a note about implicit object conversion being (unfortunately) opera-centric.

By Benjamin Joffe O anchor Thursday, 16. November 2006, 12:21:01

avatarHere are three little tricks I often use if they are inside loops:

------------------------------------------

I find that this is a good optimisation for charAt() inside heavy loops:

Original:
var s = new String('0123456789');
for( var i = 0, len = s.length; i < len; i++ ) {
  s.charAt(i);
}


Optimised:
var s = new String('0123456789');
var arr = s.split('');
for( var i = 0, len = arr.length; i < len; i++ ) {
  s[i];
}


This is avoids separate function calls for each letter by making a temporary array.

------------------------------------------

Instead of using Math.round(Math.random()*10), you can use (Math.random()*10)>>0. A bitwise shift of zero rounds down and avoids the extra function call.

------------------------------------------

There is only really one proper way of swapping two variables but if the variables being swapped are numbers then there are many ways:

Temporary variable:
var temp = a;
a = b;
b = temp;


Additions and subtractions:
a += b;
b = a - b;
a -= b;


Bitwise:
a ^= b;
b ^= a;
a ^= b;


However this next one I have found to by far the fastest in all major browsers, it takes advantage of the order of operations in javascript:
a = b + 0*(b=a);


------------------------------------------

These, as I said are only necessary if they are looped through many times.

By jax anchor Friday, 17. November 2006, 10:34:10

avatar

Originally posted by crisp:

my advice would be to test each and every optimization in various browsers to find out what actually gives the best result in all of them.


The goal of the Efficient series is to highlight good practices that should, at least in theory, be fairly browser independent.

While testing the result in different browsers is necessary, the result won't only vary from browser to browser, but from browser version to browser version. If one method is much slower in one browser than another, there are good chances that it will be better optimised in the next version. However, if one method is consistently much slower among all browsers it is likely that that method is inherently more inefficient (or that nobody has figured out a way to make it efficient yet).

By Salsero_Nash anchor Saturday, 18. November 2006, 19:50:20

avatarHow I can add that JavaScript to work in Opera???.

By crisp anchor Sunday, 19. November 2006, 21:20:13

avatar

Originally posted by jax:

The goal of the Efficient series is to highlight good practices that should, at least in theory, be fairly browser independent.

And I may add that you are doing a better job at that than Microsoft with their performance recommendations :wink:

I still think that a warning for premature optimization would be good. That doesn't count for all tips here, most of them can indeed be labelled 'good practice', but some could degrade readability and maintenability of your code and such optimizations should only be used as a last resort and when it is absolutely necessary. Most of the time however you may find that performance issues are due to general bad design of your code or the overly use of abstrations that in itself slow things down ($() anyone?).

By liorean anchor Saturday, 16. December 2006, 20:43:00

avatarAbout building strings: I've done some performance checking on the building of (massive) strings in various browsers. My tip here depends on browser relevance - Internet Explorer/JScript is several orders of magnitude slower than the other browsers and consumes extreme amounts of memory doing it if you build strings using the + operator, the += operator or even the String.prototype.concat method. For JScript, the ONLY efficient way of building a large string from many small strings is using the Array.prototype.join method.

Other browsers?

If all the small strings are compile time constants, Mozilla-Seamonkey-Firefox-Camino-Gecko/SpiderMonkey seems to be the only browser doing compile time string constant folding. This means that the browser will add the strings together when parsing instead of when executing and thus you don't have lots of intermediaries, the string building doesn't consume processing time, there's no risk of triggering GC or making the browser generally unresponsive while doing it, etc.
However, most of the time, the string isn't made from lots of compile time constants, it's built from runtime data. So, this optimisation, while nice to experience, doesn't really help in the majority of cases. In more common usage, SpiderMonkey has best performance using String.prototype.concat, and worst performance using Array.prototype.join, with += and + operators falling somewhere in the middle.

For Opera, the performance is best using String.prototype.concat, but with just a small difference for the other methods. Also, Opera is blazingly fast compared to SpiderMonkey.

For Safari-OmniWeb-Shiira-Swift/WebKit browsers, String.prototype.concat has best performance, followed by Array.prototype.join. Here it can be noted however that performance of the + operator or += operator is really bad. WebKit using String.prototype.concat has a performance similar to Opera using the same method, but using the + operator takes on the order of ten times more time in WebKit compared to Opera.

CONCLUSION: The performance loss of using Array.prototype.join for SpiderMonkey (or the much more modest performance loss in WebKit and Opera for that matter) is more than compensated for by the fact it's the ONLY method of string building that has reasonable performance in JScript. However, if JScript performance is not of relevance, String.prototype.concat has consistently best performance.


Some data: a very artificial and contrived test - a test which builds a single string from ten thousand random small strings in hundred iterations (With the exception of JScript only doing a single iteration. Why? Because that single iteration takes about as much time as a hundred iterations does in the others.)

________________ff2.0_____op9.10_____swift0.2_________ie6w__
String concat   1 063ms      500ms        468ms      1 094ms
Array join      3 657ms      610ms        547ms         47ms*
+ op/vars       1 078ms    1 140ms      8 078ms      1 453ms
+ op/lits           0ms      938ms     10 782ms      1 125ms
+= op           1 484ms    1 110ms      5 984ms      4 625ms

NOTE: ie6w timings are doing ONE iteration,
other browser timings are doing ONE HUNDRED iterations.

* This one I tried with 100 iterations as well. The result then was 515ms.

Post edited Saturday, 16. December 2006, 21:19:16

By MarkSchenk anchor Tuesday, 19. December 2006, 22:19:36

avatarA follow-up to my earlier question about profiling javascript. I downloaded and installed the Firebug extension for Firefox today, to sort out some performance problems. It *does* include a profiler, and some handy JS debugging stuff:

http://getfirebug.com/js.html

All in all it's one hell of an extension! The DOM/CSS editor is very well done, and Opera should definitely take a cue from this extension, for the implementation of their own webdevelopment tools.

A pity this great extension only serves to show FF's performance current JS problems, compared to Opera :-)

By OperaTheBest2 anchor Friday, 29. December 2006, 21:55:56

avatarMy code is efficient?
http://agoohttp.atspace.com/AproLang/manual.html

(I use only Opera on Linux)

Where I can post my open source code, on dev.opera ?
Thanks

Bye

By lovedada anchor Friday, 11. May 2007, 23:06:04

avatarNice article. I'm particularly interested in http://dev.opera.com/articles/view/efficient-javascript/?page=2#forin about avoiding for (<key> in <object>). Specifically, "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". Why would iterating throug an associative array entail removing duplicates ? Wouldn't any reasonable implementation remove duplicates on entry to the array ?

I filed a bug (261749) a few weeks ago about how large associative arrays in Opera perform very poorly compared to other browsers but have not heard a thing about it so far.

By echagaray anchor Thursday, 9. August 2007, 04:20:13

avatarHi. Just new to this forum site, I just want to share something.

I found this guy's script, after searching for a more simple javascript rollover script. It comes from a guy on the internet named Jehiah Czebotar. Just want to see what you guys think. It also preloads the images without extra effort.

In the head or headers it goes like this:

<script type="text/javascript">
function SimpleSwap(el,which){
el.src=el.getAttribute(which ¦¦ "origsrc");
}
function SimpleSwapSetup(){
var x = document.getElementsByTagName("img");
for (var i=0;i<x.length;i++){
var oversrc = x.getAttribute("oversrc");
if (!oversrc) continue;

// preload image
// comment the next two lines to disable image pre-loading
x.oversrc_img = new Image();
x.oversrc_img.src=oversrc;
// set event handlers
x.onmouseover = new Function("SimpleSwap(this,'oversrc');");
x.onmouseout = new Function("SimpleSwap(this);");
// save original src
x.setAttribute("origsrc",x.src);
}
}
var PreSimpleSwapOnload =(window.onload)? window.onload : function(){};
window.onload = function(){PreSimpleSwapOnload(); SimpleSwapSetup();}
</script>

In the body it goes like this:

<img src="image1.gif" oversrc="image2.gif">

By Benjamin Joffe O anchor Thursday, 9. August 2007, 09:13:39

avatar@echagaray:
For hover effects you are better off avoiding javascript and using CSS:

<style type="text/css">
#myPic {
  display: block;
  width: 200px;
  height: 100px;
  background-image: url(image1.gif);
}
#myPic:hover {
  background-image: url(image2.gif);
}
</style>


<a href="# id="myPic"></a>

By misha3d anchor Friday, 21. September 2007, 17:52:00

avatarExcellent article.

Though I noticed that changing a number of elements is about 20% faster than cloning the parent node, doing the canges to the cloned tree fragment and then exchanging the nodes.


Example:
I have 150 div's inside a node. I'm moving all 150 div's 25 times per second by iterating through them.
This doesn't cause any reflow to the document, so it is solely a repaint.
The example can be found at: http://playground.3d-m.de/js_starfield_alpha.html

Modifying the nodes directly occupies my MacBook Pro 2.16GHz 43%.

Cloning the parent and doing the changes to the cloned nodes keeps the system loaded at 53%.

The difference is even more obvious with Mozilla based browsers.

By justinbmeyer anchor Saturday, 19. January 2008, 01:30:51

avatarOn using document.write to load script tags ...

Is there a way to have Opera not load JavaScript immediately. IE, FF, and Safari load after the current 'thread' of execution terminates. For example:

I load a file (first.js) that calls alert(1), and then call alert(2)

<script language="javascript" type="text/javascript">
document.write("<script type='text/javascript' src="first.js"><\/script>");
alert(2)
</script>

In other browsers, I see 1 then 2. In Opera, 2 then 1. Although Opera's way makes more sense. I'm trying to load scripts in a consistent order. I've written a script to help with this. Unfortunately, I can't get it to work with Opera.

Eventually, I want to make an automatic compressor. Developers will be able to include any JS file they want. My compressor will parse the JS files, and collect all the included JS.

But, I need to get this working in Opera first.

By gregersrygg anchor Thursday, 28. February 2008, 09:09:32

avatar

Originally posted by justinbmeyer:

Eventually, I want to make an automatic compressor. Developers will be able to include any JS file they want. My compressor will parse the JS files, and collect all the included JS.


You know there are several tools for compressin and including javascript already...?
Just to name a few:

Yahoo! YUI Compressor:
http://developer.yahoo.com/yui/compressor/ (jar based on Rhino)
http://www.julienlecomte.net/blog/2007/09/16/ The author of the Yahoo compressor has a blog entry how to use it in your ant build.
I personally believe this is the best tool for compressing JS safely.

Include:
http://javascriptmvc.com/learningcenter/include/index.html
Recently announced on Ajaxian: http://ajaxian.com/archives/include-pack-your-javascript-with-ease

JSLoader:
http://vps.jsloader.com/
On Ajaxian a while ago (lots of good comments as well) http://ajaxian.com/archives/jsloader-on-demand-javascript-libraries

JSAN (CPAN for JS)
http://openjsan.org/

Maybe none of them is what you're looking for, but they might help you solve your problem :wink:

By gregersrygg anchor Thursday, 28. February 2008, 09:37:23

avatarOh, I almost forgot. Here is a browser-independent profiler from Yahoo:
http://developer.yahoo.com/yui/profiler/

You have to specify what functions / objects / classes you want to profile, but it helps a lot to find bottlenecks without Firebug.

Moderators: jax | malware | mcx | operadev