setTimeout() and string literals - stuck

M

mhallatt

I don't get this:

for(i=0; i<=100; i++) {

setTimeout("someFunction(" + i + ")",5000);

}

what is happening to 'i' here that isn't in:

setTimeout('someFunction(i)',5000);


function someFunction(opacity) {

var object = document.getElementById('someElement').style;
object.opacity = (opacity / 100);
}
 
S

Stevo

I don't get this:

for(i=0; i<=100; i++) {
setTimeout("someFunction(" + i + ")",5000);
}

what is happening to 'i' here that isn't in:

setTimeout('someFunction(i)',5000);

Are you joking or serious ? Every one of those calls to someFunction is
going to get the value of that global variable i after 5 seconds. So all
of the calls will pass the value 100, assuming you're not doing
something else with the global variable i by that time. The first
example, takes the current value of the global variable i and inserts it
into the string of JS that will later be eval'ed by setTimeout (e.g.
someFunction(0), someFunction(1) etc).
 
D

Doug Gunnoe

The first example, takes the current value of the global variable i and inserts it
into the string of JS that will later be eval'ed by setTimeout (e.g.
someFunction(0), someFunction(1) etc).

I think what he is saying is that the string concatenation is not
working properly.
setTimeout("someFunction(" + i + ")",5000);

But it works in both IE7 and Firefox for me.
 
D

Doug Gunnoe

I don't get this:

for(i=0; i<=100; i++) {

setTimeout("someFunction(" + i + ")",5000);

}

what is happening to 'i' here that isn't in:

setTimeout('someFunction(i)',5000);

function someFunction(opacity) {

        var object = document.getElementById('someElement').style;
        object.opacity = (opacity / 100);

And what Stevo said.

try
for(var i=0; i<=100; i++)
 
D

dhtml

I think what he is saying is that the string concatenation is not
working properly.
He's wrong.

The problem is in the callback. Don't forget var.

Try something simple:

for(var i=0; i<=100; i++) {

setTimeout("someFunction(" + i + ")",5000);

}

function someFunction(x) {
console.log(x);
}
But it works in both IE7 and Firefox for me.

Yes the loop works.


this expression gets evaluated in each iteration, so you get our loop
doing this:

setTimeout( "someFunction(0)", 5000 )
setTimeout( "someFunction(1)", 5000 )

...
 
T

timothytoe

Any time I see setTimout() called with a goofy string expression, I
figure the user is fighting a closure.

This is not C we're dealing with here (although it LOOKS like C).
Javascript is crazy expressive with functions, and you have to learn
what's going on with all the ways you can define and use functions.

First of all, let's get rid of the string passed in and let's pass in
an anonymous function.

Compare the two versions below. The first loop displays "first 5" five
times. Probably not what you want, but it's what we asked for. Why?
Because i is 5 by the time even the first alert is shown. You're in a
closure, and the function maintains the scope that existed when it was
called. That _doesn't_ mean the variables have the values they had
when setTimeout() was called. Instead, it means that scope is kept
around and is still active. In fact, you could change i in one call of
setTimeout() and see in the next call that the new value of i holds.

The second loop is probably what you want. The alerts come out:

second 0
second 1
second 2
second 3
second 4

<code><pre>
var i;
for (i=
for (i=0;i<5;i++) {
setTimeout(function() {alert('first '+i);},5000);
}
for (i=0;i<5;i++) {
(function(num) {
setTimeout(function() {alert('second '+num);},5000);
})(i);
}
</pre></code>

At this point, the questioner should be able to use the second pattern
to rewrite his code without the string concatenation, and maybe even
understand what is going on.

Read about:
JavaScript closures
Anonymous functions

This probably works for you:

<code><pre>
for (i=0;i<5;i++) {
setTimeout("alert("+i+")",5000);
}
</pre></code>

It works for me. But I think the preferred way to call setTimeout()
nowadays is to call it with a function as the first parameter, not a
string. So use the anonymous function solution above, because it will
help you learn how JavaScript functions work.
 
T

timothytoe

I see I don't need pre or code to mark my scripts here. Also, I seem
to have somehow introduced a bug while pasting. Correction below. The
discussion stands.

var i;
for (i=0;i<5;i++) {
setTimeout(function() {alert('first '+i);},5000);
}
for (i=0;i<5;i++) {
(function(num) {
setTimeout(function() {alert('second '+num);},5000);
})(i);
}
 
T

Thomas 'PointedEars' Lahn

I don't get this:

for(i=0; i<=100; i++) {

setTimeout("someFunction(" + i + ")",5000);

}

what is happening to 'i' here that isn't in:

setTimeout('someFunction(i)',5000);

It is quite obvious. In both cases, the current value of `i' is used.
However, what is "current" is different in both cases.

In the former case, "current" means the current loop where the value of
`i' changes with every loop and so the value of the evaluated string
concatenation that is interpreted as code by the timer method.

In the latter case, current means the value of `i' after arbitrary
operations have been performed on `i'. Suppose someFunction(i) is invoked
directly after the loop has completed, the current value of `i' would be 101
and so the call would be equivalent to someFunction(101) as `i' is not
evaluated before the call expression is evaluated. (Your confusion might be
caused by the misguided but common inference that variable identifiers would
be expanded in ECMAScript string literals like they are in double-quoted
PHP/Perl/sh strings, for example. In that sense, you could think of ES
string literals as single-quoted PHP/Perl/sh strings, where no variable
expansion takes place.)

Besides maybe not declaring your identifiers as mentioned already, there is
another issue with your code that AFAIS has not been mentioned yet: with the
above loop, all calls to someFunction() are executed at approximately the
same time as the second argument of window.setTimeout() is not increasing
and the execution delay between the loops is negligible. You are looking
for something like this instead:

for (var i = 0; i <= 100; i++)
{
window.setTimeout("someFunction(" + i + ")", (i + 1) * 5000);
}

(There is also the possibility to use a Function object reference as
argument, as in

for (var i = 0; i <= 100; i++)
{
window.setTimeout(function() { someFunction(i) }, (i + 1) * 5000);
}

However, that would create a closure which forced you to work around it.)

Probably you want to consider decreasing the value of 5000 considerably, as
a 5 second delay for each of the 100 steps would take the entire animation
too long (500'000 ms = 8:20 min). Beware not to let the argument value drop
below 10 (ms) though, as inconsistent (and even devastating) results are
likely then.


PointedEars
 
M

mhallatt

I really appreciate the explanations/advice. Thanks.
Brian Reindel (http://blog.reindel.com/2006/09/24/pass-parameters-to-
your-javascript-functions-correctly-from-settimeout-using-oop/) has a
paragraph on setTimeout that helped me get my head around the
complexities of its use (for a newcomer like me):

"If you are moving beyond using JavaScript for simple tasks, and are
building full fledged Web applications, then you will likely make
heavy use of the setTimeout() method available to the window object.
This timer can be powerful, but it also possesses a single shortcoming
that stumps even experienced programmers. There is no "built-in"
mechanism within setTimeout() for calling a function that needs
parameters passed to it...I won't go into the details for all the
different workarounds that currently exist in order to pass in
parameters, as several forums and Web sites do so already. I will
mention that there are two popular techniques: placing an entire
function inside the first parameter (not as a string [i.e. - without
the quotes]), and attempting to use addition operators to build an
expression that represents a function (as a string)."

He advocates an object oriented approach and posts an example.

Per my immediate question about what "+ i +" does, from reading
Reindel I figure I'm "attempting to use addition operators to build an
expression that represents a function (as a string)" whereas Thomas
and timothytoe opt for the "function inside the first parameter"
technique.

The result of the code (see below) is a smooth fade in of an image.
Thomas's suggestion didn't produce the same smooth fade in result as
"+ i +" but timothytoe's did.

So of the approaches (assuming a closure workaround for Thomas's -
beyond me) , what are the relative merits I wonder? (My emphasis as a
newcomer is readability over performance).

Here's what I ended up with (options in a comment block):

function fadeIn() {
var speed = Math.round(500 / 100);
for(var i=0; i<=100; i++) {

/*
Option A per my post:
setTimeout("changeOpacity(" + i + ")",(i * speed));
Option B per timothytoe:
(function(num) {
setTimeout(function() { changeOpacity(num)}, (i *speed));
})(i);
Option C per Thomas: setTimeout(function() { changeOpacity(i) }, (i
* speed));
*/

}
}

function changeOpacity(opacity) {
var object = document.getElementById('someImage').style;
object.opacity = (opacity / 100);
}
 
T

Thomas 'PointedEars' Lahn

Per my immediate question about what "+ i +" does, from reading
Reindel I figure I'm "attempting to use addition operators to build an
expression that represents a function (as a string)" whereas Thomas
and timothytoe opt for the "function inside the first parameter"
technique.

My pointing out that you would need to work around the closure that is
created by the Function object reference should be understood as a
recommendation in favor of the string concatenation approach in this case.
That one is also fully backwards-compatible (back to the point where the
method was introduced first).
The result of the code (see below) is a smooth fade in of an image.
Thomas's suggestion didn't produce the same smooth fade in result as
"+ i +"

You are mistaken about what I suggested. Please read my article again.
but timothytoe's did.

So of the approaches (assuming a closure workaround for Thomas's -
beyond me) ,

Understanding closures should be considered an important milestone in the
learning curve of everyone dealing with ECMAScript implementations like
J(ava)Script.

Start with http://en.wikipedia.org/wiki/Closure_(computer_science), then
continue with http://www.jibbering.com/faq/faq_notes/closures.html
what are the relative merits I wonder? (My emphasis as a
newcomer is readability over performance).

You can pass an object reference which you cannot or only with greater
effort and more potential side-effects with string concatenation. The
former is most useful if you want to implement an animation as an object
where you would want the object to act like a FSM, going from one frame
(or state) to the next.
Here's what I ended up with (options in a comment block):

function fadeIn() {
var speed = Math.round(500 / 100);
for(var i=0; i<=100; i++) {

This executes the loop *101* times. Are you sure you want that?
Option B per timothytoe:
(function(num) {
setTimeout(function() { changeOpacity(num)}, (i *speed));
})(i);

For there to be a point to this call, it would have to use only `num' in
place of `i' within the called function; otherwise the timeouts are likely
to accumulate as they did before (i * speed = const.)

The function call is one possibility to work around the closure, as it
defines the value of `num' (limited to the local execution context of the
function) to be the value of `i' that is current when the function is called.
Option C per Thomas: setTimeout(function() { changeOpacity(i) }, (i
* speed));

See above.
*/
}
}

function changeOpacity(opacity) {
var object = document.getElementById('someImage').style;
object.opacity = (opacity / 100);

The `opacity' property is not supported by IE/MSHTML. See previous
discussions; there really is no need to reinvent the wheel every time.


PointedEars
 
J

John Nagle

Thomas said:
(e-mail address removed) wrote:

This executes the loop *101* times. Are you sure you want that?

Actually, queuing up 100 timeouts all at once is probably not what you
want. Try scheduling one timeout at a time, as each previous one finishes.

Also, bear in mind that Javascript execution is still rather slow. You can
delay the user's browsing this way. Someday we'll have the Tamarin
JIT compiler in Firefox, but it's not there yet. Incidentally, once we
get a JIT compiler, it will be much faster to execute closures than to
recompile from strings.

John Nagle
 
T

Thomas 'PointedEars' Lahn

John said:
Actually, queuing up 100 timeouts all at once is probably not what you
want. Try scheduling one timeout at a time, as each previous one
finishes.
Correct.

Also, bear in mind that Javascript execution is still rather slow. You
can delay the user's browsing this way.

I think that is unlikely. It is more likely that the timeouts accumulate
and therefore the animation is not going to be a smooth one.
Someday we'll have the Tamarin JIT compiler in Firefox, but it's not
there yet. Incidentally, once we get a JIT compiler, it will be much
faster to execute closures than to recompile from strings.

At least Mozilla/5.0 already uses a JIT compiler, that of SpiderMonkey.
Script code is JIT-compiled into byte-code that is executed by the JSVM.

http://developer.mozilla.org/en/docs/SpiderMonkey


PointedEars
 
T

timothytoe

I think that is unlikely. It is more likely that the timeouts accumulate
and therefore the animation is not going to be a smooth one.


At least Mozilla/5.0 already uses a JIT compiler, that of SpiderMonkey.
Script code is JIT-compiled into byte-code that is executed by the JSVM.

http://developer.mozilla.org/en/docs/SpiderMonkey

PointedEars
--
realism: HTML 4.01 Strict
evangelism: XHTML 1.0 Strict
madness: XHTML 1.1 as application/xhtml+xml
-- Bjoern Hoehrmann

I think the expectation of the large JavaScript speed increases coming
in Mozilla are due to the eventual inclusion of the Adome Tamarin
"tracing JIT" system making its way into the Mozilla code. Tests show
that tracing JIT makes a big difference (a factor of 10 faster or more
on simple computationally-intensive benchmarks), but I'll reserve
judgment on that until I see what it does with my code on my site. I
just tested my site on Firefox 3 Beta 3 and I saw no such miraculous
improvement there. Firefox 3b3 was about as fast with my code as
Safari is (almost twice as fast on my code as Firefox 2), which is
good, but not revolutionary. So at this point I'm assuming that
tracing JIT is not yet in the main trunk.

But when you worry about speed, it usually makes sense to worry about
slow browsers, not fast ones.

PointedEars is right. No matter which solution you use, it's important
to know closures and anonymous functions in JavaScript. Learning them
gives you quite a bit of expressive power in the language. Not
learning about them means you have a chance of being baffled every
time you create a function within a function. In JavaScript within a
browser, that's often indeed. It's not just SetTimeout() that'll get
you, but setting up event handlers as well.

--TT
 
M

mhallatt

thanks guys for this. Besides having the sensation of my head being
about to explode I'm sensing comprehension around the corner. Far
corner.

To that end I submit (with trepidation) the following:

I'm getting back from my php page some data from an sql query which I
send to the client as an object literal:

passUserProjectsData( [ { projectname: 'test18', jobcategory: 'other',
datecreated: '2008-02-07 17:30:56'},{ projectname: 'testtx',
jobcategory: 'other', datecreated: '2008-02-07 20:26:41'} ] );

I've chosen to present the data in the client with javascript.

I want to initialize some textNode vars dynamically from the object
passed in rather than hard coding the keys.

Things got 'interesting' pretty quickly. I ended up here: (caveat:
this is not the final intended code but a way for me to get there and
I don't really fully comprehend why/how this works)


function passUserProjectsData(Dataset) {
for (j in dataSet[0]) {
this[j] = document.createTextNode(dataSet[0].eval(j));
}
alert(window.projectname.data);
alert(window.jobcategory.data);
alert(window.datecreated.data);
}

This produced the expected result in so far as what appeared in the
alert boxes. Clicked heels.
Further to the recommendations in this thread per learning closures
and anonymous functions as a foundation, what is my next step in
optimizing this function? (Should eval() be swapped out for an
anonymous function? If so, what would be in the function to resolve
j?) Am I even close?


Mike
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,997
Messages
2,570,240
Members
46,829
Latest member
KimberAlli

Latest Threads

Top