From: Eleandor on
Well, I realise it's not so much a bug as it is a feature.

I've written a quick testpage:

<html>
<head>
<script type="text/javascript">
Person = (function() {
return function() {
var self = this;

this.messages = function() {
for(var i = 0; i < 10; i++) {
var elm = document.createElement("div");
elm.innerHTML = "Display " + i;
elm.onclick = function() {
self.shout(i);
};
document.body.appendChild(elm);
}
};

this.shout = function(number) {
alert(number);
};
};
}());

function LoadPage() {
var p = new Person();
p.messages();
}

</script>
</head>

<body onload="LoadPage()">
</body>
</html>

The html page shows "Display x" with x a number from 0 to 9. However,
as I click it, it displays 10 regardless of which element I clicked. I
can understand why, since the value of i is 10 when the loop ends. But
what I'm trying to achieve is to create a function that uses the
actual value of i, at the moment when the onclick function is created.
So "Display 5" should actually display 5, using the literal value of i
when the function is created, instead of the value of i at the time of
execution.

How can I achieve something like this?

Thanks,

- Bart
From: RobG on
On May 17, 8:44 am, Eleandor <vanbeurden.b...(a)gmail.com> wrote:
> Well, I realise it's not so much a bug as it is a feature.
>
> I've written a quick testpage:
>
> <html>
>         <head>

When posting code, indent using 2 (preferred) or 4 spaces and manually
wrap at about 70 characters, see below.

<head>
<script type="text/javascript">

Person = (function() {
return function() {
var self = this;
this.messages = function() {

for(var i = 0; i < 10; i++) {
var elm = document.createElement("div");
elm.innerHTML = "Display " + i;
elm.onclick = function() {
self.shout(i);
};
document.body.appendChild(elm);
}
};
this.shout = function(number) {
alert(number);
};
};
}());

function LoadPage() {
var p = new Person();
p.messages();
}
</script>
</head>
<body onload="LoadPage()"></body>

>
> The html page shows "Display x" with x a number from 0 to 9. However,
> as I click it, it displays 10 regardless of which element I clicked. I
> can understand why, since the value of i is 10 when the loop ends. But
> what I'm trying to achieve is to create a function that uses the
> actual value of i, at the moment when the onclick function is created.
> So "Display 5" should actually display 5, using the literal value of i
> when the function is created, instead of the value of i at the time of
> execution.
>
> How can I achieve something like this?

You need to break the closure. One way is to use new Function, but
scope becomes a bit tricky. Another is to use a setter for the onclick
property rather than a function expression, e.g.

elm.onclick = setOnclick(self, i);


then add:

function setOnclick(self, i) {
return function() {
self.shout(i);
}
}

as a global function, or inside the outer Person function (the
Cornified one[1]).

Another is to set it with a function expression:

elm.onclick = (function(number) {
return function() {
self.shout(number);
};
})(i);


But this whole thing seems quite convoluted. The i property doesn't
seem to belong to a "person", it belongs to the listener attached to
the element, so probably shouldn't be inside the person constructor at
all. Perhaps you are better to create a message cache and use an
attribute of the HTML element to associate with a message (e.g. its
ID). If HTML 5 was widely implemented, you could add the index as a
data property of the element directly (but it will be a long time
before that is viable on the web).

There are many other solutions, hard to say what is better or worse
without knowing what you are really trying to do, I doubt that either
of the above suggestions are the best you can do in your circumstance.

1. <URL: http://groups.google.com/group/comp.lang.javascript/browse_frm/thread/c6fec4b30a4e2811#
>


--
Rob
From: Thomas 'PointedEars' Lahn on
RobG wrote:

> Eleandor wrote:
>> I've written a quick testpage:
>>
>> <html>
>> <head>
>
> When posting code, indent using 2 (preferred) or 4 spaces and manually
> wrap at about 70 characters, see below.

ACK

> <head>

That HTML document needs a DOCTYPE declaration and a TITLE element to be
Valid. <http://validator.w3.org/>

> <script type="text/javascript">
>
> Person = (function() {

Should be declared a (global) variable, otherwise there can be fatal
side-effects with MSHTML (fatal as in "breaking").

var Person = ...

> return function() {
> var self = this;
> this.messages = function() {
>
> for(var i = 0; i < 10; i++) {
> var elm = document.createElement("div");
> elm.innerHTML = "Display " + i;
> elm.onclick = function() {
> self.shout(i);
> };
> document.body.appendChild(elm);

You should avoid combining DOM Level 2+ and 0, especially when without
feature test. In particular, you do not need or want `innerHTML' here:

var elm = document.createElement("div");
if (elm)
{
elm.appendChild(document.createTextNode("Display " + i));
// ...
}

> }
> };
> this.shout = function(number) {
> alert(number);
> };
> };
> }());
>
> function LoadPage() {

This is not used as a constructor or factory, so the identifier should start
lowercase.

> [...] or inside the outer Person function (the Cornified one[1]). [...]
^^^^^^^^^
Please don't. I thought it was only a joke back then :-/


PointedEars
--
Use any version of Microsoft Frontpage to create your site.
(This won't prevent people from viewing your source, but no one
will want to steal it.)
-- from <http://www.vortex-webdesign.com/help/hidesource.htm> (404-comp.)
From: Matt Kruse on
On May 16, 5:44 pm, Eleandor <vanbeurden.b...(a)gmail.com> wrote:
> what I'm trying to achieve is to create a function that uses the
> actual value of i, at the moment when the onclick function is created.
> So "Display 5" should actually display 5, using the literal value of i
> when the function is created, instead of the value of i at the time of
> execution.

elm.onclick = (function(inner_i) {
return function() {
self.shout(inner_i);
}
})(i);

Be careful for memory leaks...

Matt Kruse
From: williamc on
On 5/16/2010 6:44 PM, Eleandor wrote:
> Well, I realise it's not so much a bug as it is a feature.
>
> I've written a quick testpage:
>
....
>
> The html page shows "Display x" with x a number from 0 to 9. However,
> as I click it, it displays 10 regardless of which element I clicked. I
> can understand why, since the value of i is 10 when the loop ends. But
> what I'm trying to achieve is to create a function that uses the
> actual value of i, at the moment when the onclick function is created.
> So "Display 5" should actually display 5, using the literal value of i
> when the function is created, instead of the value of i at the time of
> execution.
>
> How can I achieve something like this?
>
> Thanks,
>
> - Bart


Working through the example below helped me when I was reading the Zakas
book not too long ago. From a notes page...

* * *

4. Inner functions that retain values from outer functions possess the
last value from the outer function. This can lead to unexpected results
as demonstrated in the first function below, which the programmer
expected to return an array of functions which each will return the
value of their array index. Instead, each function returns 5.

The second function creates the desired array of functions. Zakas: "The
anonymous function has one argument, num, which is the number that the
result function should return. Since function arguments are passed by
value, the current value of i is copied into the argument num."

function createFunctions() {
var result = new Array();
for (var i = 0; i < 5; i++) {
result[i] = function() {
return i;
};
}
return result;
}
arrTest = createFunctions();
alert(arrTest[2]()); // 5, not 2!


function createFunctions2() {
var result = new Array();
for (var i = 0; i < 5; i++) {
result[i] = function(num) {
return function() {
return num;
};
}(i);
}
return result;
}
arrTest = createFunctions2();
alert(arrTest[2]()); // now it's 2