Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I don't fully understand JavaScript Threading

Before I dive into the question. Let me state that by Event Loop I am referring to http://en.wikipedia.org/wiki/Event_loop. This is something that browsers implement. For more information, read this: http://javascript.info/tutorial/further-javascript-features/events-and-timing-depth.

This question is hard and long, so, please try to bear with it! And I do appreciate all answers!


So. Now, as I understand it, in JavaScript there is a single main thread (in most browser environments, that is). So, code like:

for (var color = 0x000; color < 0xfff; color++) {
    $('div').css('background-color', color.toString(16));
}

will produce an animation from black to white, but you won't see that because the rendering is done after the code has been processed (when the next tick happens -- the browser enters the Event Loop).

If you want to see the animation, you could do:

for (var color = 0x000; color < 0xfff; color++) {
    setTimeout(function() {
        $('div').css('background-color', color.toString(16));
    }, 0);
}

The above example would produce a visible animation, because setTimeout pushes a new event to the browser Event Loop stack which will be processed after there is nothing running (it enters the Event Loop to see what to do next).

It seems that the browser in this case have 0xfff (4095) events pushed into the stack, where each of them are processed with a render process in between them. So, my first question (#1) is that when exactly does the rendering take place? Does it always take place in between the processing of two events in the Event Loop stack?


The second question is about the code in the javascript.info website link I gave you.

...
  function func() { 
    timer = setTimeout(func, 0)
    div.style.backgroundColor = '#'+i.toString(16)
    if (i++ == 0xFFFFFF) stop()
  }

timer = setTimeout(func, 0)
....

My question here is that will the browser push a new "rendering" event to the Event Loop stack every time it reaches the div.style. ... = ... line? But does it not first push an event due to the setTimeout-call? So, does the browser end up in a stack like:

setTimeout event
render event

Since the setTimeout call was processed before the div style change? If that's how the stack looks like, then I would assume the next time the browser enters the Event Loop it will process the setTimeout's callback and end up having:

rendering event
setTimeout event
rendering event

and continue with the rendering event that the earlier setTimeout call produced?

like image 502
Tower Avatar asked Mar 05 '11 12:03

Tower


2 Answers

Q1: Not necessarily. Browsers to varying degrees implement optimizations. For example, they may wait to collect several style changes before triggering an expensive recalculation of the layout. So the answer is: depends on the specific browser.

Try this: http://taligarsiel.com/Projects/howbrowserswork1.htm#Render_tree_construction (the document is dated Oct 2009 - i.e. it is sufficiently up to date)

Q2: The rendering is not necessarily the same as the JS execution - that's two different engines. Ths JS engine is not responsible for the rendering, it just interfaces with the render engine. It seems to me the main message for your second question is this independence of the JS from the rendering engine. Remember, a browser (or a webpage) does not need Javascript, their main purpose is to render HTML based on CSS style rules. Javascript is just one way to manipulate the HTML (the DOM tree really) and the style rules.

Note that you can force rendering by reading a style definition - at this point the rendering engine has no choice but process any outstanding style changes, especially if it involves any position changes. That's why one should remove objects from the rendering tree (e.g. by setting display:none - visibility:hidden is NOT enough since the element's size is still considered for layout) before doing a lot of style changes or adding a lot of elements, e.g. when lots of rows are added one by one (a "for" loop) to a table.

Not part of the question at all - but since I just mentioned a difference between display:none and visibility:hidden, that's also a consideration when adding hidden position:absolute elements like dialogs. While there is no visible difference whether an absolutely positioned element is hidden from you using one or the other method, internally there IS a big difference: when hidden using visibility:hidden the element is part of the rendering tree, with display:none it is not. So, if one has such an element that needs to be toggled a lot one should use visibility:hidden, because when the "display" style is switched between "none" and e.g. "block" the browser has to render it first.

like image 57
Mörre Avatar answered Nov 03 '22 09:11

Mörre


The article you mention only considers Javascript. A lot more happens in the browser; reflowing and repainting are/can be triggered by a lot more things; take a look at the following links for more info on this.

  • http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/
  • http://www.browserscope.org/reflow/about

I wouldn't use setTimeout for this purpose.

Edit: As per the comments, the recommended way is to use requestAnimationFrame. As of this writing, this is only available in unstable releases of most browsers. There are however several libraries available providing cross-browser access to it, and fall back to using setTimeout if necessary.

Take a look at this demo for an example working in old browsers, as well as in new ones: http://paulirish.com/2011/requestanimationframe-for-smart-animating/

like image 44
Martijn Avatar answered Nov 03 '22 10:11

Martijn