Prev: Microsoft Visual Web Developerr
Next: FAQ Topic - What is the Document Object Model (DOM)? (2010-05-17)
From: Eleandor on 16 May 2010 18:44 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 16 May 2010 20:08 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 16 May 2010 22:57 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 17 May 2010 13:56 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 17 May 2010 13:59
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 |