weird when using onload in a for-loop [duplicate] - javascript

This question already has answers here:
Javascript infamous Loop issue? [duplicate]
(5 answers)
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
I am making a game using JavaScript and HTML canvas. The 'onload' works perfectly without a for-loop. But 'onload' become buggy and unreasonable in a for-loop. However, I came up with a simple workaround to skip that problem. But I am still curious why. Here comes the codes and how is the workaround:
I have created an object:
function Monster() {
this.image = new Image();
this.ready = false;
}
and created to a few (let's say two) instances:
var monster = [];
for(var a = 0; a < 2; a++) {
monster.push(new Monster());
}
When I tried to initialize these objects, it's gonna fail:
for(n = 0; n < monster.length; n++) { //assume length is 2
monster[n].image.onload = function() {
monster[n].ready = true; /* Problem raise up here, n has the same value as monster.length.
If length is 2, this line gonna run 2 times */
};
monster[n].image.src = 'images/m1.png';
}
However, this problem could be solved easily by creating a function:
for(n = 0; n < monster.length; n++) {
makeMonster(n);
}
And:
var makeMonster = function(n) {
monster[n].image.onload = function() {
monster[n].ready = true;
};
monster[n].image.src = 'images/m1.png';
};
The question is, why?

The onload function is async, so by the time it fires the loop has completed and the value of n is whatever it was set to last.
You have to lock the value of the n variable in a new scope with a closure, creating a new function also creates such a scope, or like this
for(n = 0; n < monster.length; n++) {
(function(k) {
monster[k].image.onload = function() {
monster[k].ready = true;
}
monster[k].image.src = 'images/m1.png';
})(n);
}

Related

How to fix scope issues when using an onclick event in a for loop [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 3 years ago.
I am curretly experiencing difficulties implementing an onclick event within a for loop. Instead of alerting the respective value it always returns undefined (presumably a scope problem, because the iteration itself works fine)
Until now I tried to pass on the i variable to the onclick function; however, with little success
for (var i = 0; i < timeSpanLength; i++) {
// creating the wrap for the month
var month = document.createElement("div");
month.className = 'month_element';
var reference_month = document.createElement("span");
reference_month.innerHTML = time_span[i];
//onclick event
reference_month.onclick = function(i) {
var month_beginning = signup_date;
var month_end = time_span[i];
alert(month_end);
//searchForData(month_beginning, month_end);
};
//append to container
month.appendChild(reference_month);
document.getElementById('time_container').appendChild(month);
}
The expected outcome is to trigger an alert which displays the same month which is displayed in the span element above. I need the variable to pass it on to another function.
Any help is highly appreciated since I am beginner in javascript.
for (var i = 0; i < timeSpanLength; i++) {
(function (index) {
// creating the wrap for the month
var month = document.createElement("div");
month.className = 'month_element';
var reference_month = document.createElement("span");
reference_month.innerHTML = time_span[index];
//onclick event
reference_month.onclick = function() {
var month_beginning = signup_date;
var month_end = time_span[index];
alert(month_end);
//searchForData(month_beginning, month_end);
};
//append to container
month.appendChild(reference_month);
document.getElementById('time_container').appendChild(month);
})(i);
}
This callback function handler is forming a closure with respect to the outer scope. Also var has a function scope, so in essence the block of code can be re-written as:
var i;
for (i = 0; i < timeSpanLength; i++) {
...
//onclick event
reference_month.onclick = function(i) {
var month_beginning = signup_date;
var month_end = time_span[i];
alert(month_end);
//searchForData(month_beginning, month_end);
};
...
}
So the var i is hoisted to the top and when the loop completes the value of i is timeSpanLength.length and this is what you use to access time_span[i] and that returns undefined.
Since with var the binding remains the same, the handlers registered will be referring the last value of i in the loop.
So you either need to use let in the for-loop:
for (let i = 0; i < timeSpanLength; i++) { ... }
Or an IIFE which forms a new scope bound to each new value of i from the loop:
for (var i = 0; i < timeSpanLength; i++) {
(function(i){
reference_month.onclick = function(i) {
var month_beginning = signup_date;
var month_end = time_span[i];
alert(month_end);
//searchForData(month_beginning, month_end);
};
})(i)
}

hasDeepProperty in Node JS & JavaScript [duplicate]

This question already has answers here:
JS checking deep object property existence [duplicate]
(2 answers)
Closed 6 years ago.
After spending many hours today searching for a specific condition functionality and testing my modified finds. I thought I share my coding snippets and knowledge here. I'm not too good at answering other questions so I thought it would do to post my finds and hope it would benefit others. Since this site has been really helpful with my projects.
Anyway. At some point when you get to a stage scripting more complicated scripts. You'll most likely bump into a problem were you'll need to check deep in objects if a certain property exists. This has occurred mostly with JSON API from my experience.
Example Issue
if (someObject && someObject.nextObject && someObject.nextObject.andAnother) {
// do something if all of this exists
}
Trying to check the deepest property, when one property in the middle doesn't exist, will throw a reference error.
ReferenceError: something is not defined
At some point these can get really long and messy to work with. Which is pretty much silly.
Trying to Tidy Like That Makes JSLint Complain & Can Be Harder for Another Scripter to Read
JSLint doesn't like when code ends up like this when scripters try to reduce the amount of characters on one line. Even I find it more difficult to read with other nested conditions together:
if (someObject
&& someObject.nextObject
&& someObject.nextObject.andAnother) {
// do something if all of this exists
}
There's multiple ways of making this less clutter. Below is an example of some different versions of snippet functions for this object.
var object = {
nextObject: {
anArray: [{
value: true
}]
}
};
Boolean Function with String Perimeter
var hasDeepProperty = function (obj, pathString) {
var i, properties = pathString.split("."), l = properties.length;
for (i = 0; i < l; i++) {
if (obj.hasOwnProperty(properties[i])) {
obj = obj[properties[i]];
} else {
return false;
}
}
return true;
};
console.log(hasDeepProperty(object, "nextObject.0.value")); // returns true
console.log(hasDeepProperty(object, "nextObject.doesNotExist.value")); // returns false
Boolean Function with Array Perimeter
var hasDeepProperty = function (obj, pathArray) {
var i, properties = pathArray, l = properties.length;
for (i = 0; i < l; i++) {
if (obj.hasOwnProperty(properties[i])) {
obj = obj[properties[i]];
} else {
return false;
}
}
return true;
};
console.log(hasDeepProperty(object, ["nextObject", 0, "value"])); // returns true
console.log(hasDeepProperty(object, ["nextObject", "doesNotExist", "value"])); // returns false
Prototype Boolean Function with String Perimeter
Object.prototype.hasDeepProperty = function (pathString) {
var i, properties = new String(pathString).split("."), l = properties.length, obj = new Object(this);
for (i = 0; i < l; i++) {
if (obj.hasOwnProperty(properties[i])) {
obj = obj[properties[i]];
} else {
return false;
}
}
return true;
};
console.log(object.hasDeepProperty("nextObject.0.value")); // returns true
console.log(object.hasDeepProperty("nextObject.doesNotExist.value")); // returns false
Prototype Boolean Function with Array Perimeter
Object.prototype.hasDeepProperty = function (pathArray) {
var i, properties = pathArray, l = properties.length, obj = new Object(this);
for (i = 0; i < l; i++) {
if (obj.hasOwnProperty(properties[i])) {
obj = obj[properties[i]];
} else {
return false;
}
}
return true;
};
console.log(object.hasDeepProperty(["nextObject", 0, "value"])); // returns true
console.log(object.hasDeepProperty(["nextObject", "doesNotExist", "value"])); // returns false
I'm aware that declaring with the new keyword that isn't a function simulated as a class is discouraged. But for some reason Prototype causes slice method not to exist for string and same for hasOwnProperty for the object.
I hope others find this useful and there be something like this to be added on the next version of JavaScript.
This my first: post my own QnA. And Happy New Year everyone.

Add elements in for loop to a canvas [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I have multiple canvas elements:
<canvas class="screen" width="250" height="250" data-json="{{json_encode($board)}}"></canvas>
In my javascript I do the following:
var canvas = document.getElementsByClassName('screen');
for (var i = 0; i < canvas.length; i++) {
var podium = new Image();
podium.canvas = canvas[i];
podium.onload = function() {
var coordinates = JSON.parse(podium.canvas.getAttribute('data-json');
console.log(coordinates);
//Later on I add more images or anything else based on coordinates.
}
podium.src = 'my_podium_image.jpg';
}
But coordinates always contains the data from the last <canvas> element in my HTML.
However if I put var coordinates = JSON.parse(podium.canvas.getAttribute('data-json'); outside onload and print the output, it prints the data-json attributes from every canvas which is correct, but in the load() function it's still logs only the last canvas' data attribute. (Multiple times, because of the for loop)
I have no idea what's going. :)
Your problem is about Javascript Clousure
How do JavaScript closures work?
when that function is executed, the value of podium is the last one that was assigned.
You should use a selfexecuted function that keeps the scope of podium
var canvas = document.getElementsByClassName('screen');
for (var i = 0; i < canvas.length; i++) {
(function(podium) {
podium.canvas = canvas[i];
podium.onload = function() {
var coordinates = JSON.parse(podium.canvas.getAttribute('data-json'));
console.log(coordinates);
//Later on I add more images or anything else based on coordinates.
}
podium.src = 'my_podium_image.jpg';
})(new Image())
}
var canvas = document.getElementsByClassName('screen');
for (var i = 0; i < canvas.length; i++) {
(function(index) {
var podium = new Image();
podium.canvas = canvas[i];
podium.onload = function() {
var coordinates = JSON.parse(podium.canvas.getAttribute('data-json');
console.log(coordinates);
//Later on I add more images or anything else based on coordinates.
podium.src = 'my_podium_image.jpg';
}(i));
}
That's because "onload" event is asynchronous when it got fired your loop already reached the end

Unable to get correct value on click of elements added to the DOM [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
Why doesn't the code below print it's corresponding value when clicked?
Instead, it prints 5?
var recipeDiv = document.createElement("div");
var recipeUL = document.createElement("ul");
for (var i = 0; i < 5; ++i) {
var listNode = document.createElement("li");
listNode.innerHTML = i;
listNode.onclick = function() { alert(i); }
recipeUL.appendChild(listNode);
}
recipeDiv.appendChild(recipeUL);
addNodeToDOM(document.body, recipeDiv, 'recipe');
function addNodeToDOM(element, node, id) {
var div = document.createElement('div');
div.id = id;
div.appendChild(node);
element.appendChild(div);
}
I was able to reproduce the bug here: jsfiddle
But basically, I'm not sure if this is the convention for adding elements correctly to the DOM. If so, how come whenever I click on the list elements, it doesn't show
When, onclick handler executes, the value of i will always show 5 as the for loop ends at 5th iteration,
Use this.innerHTML,
listNode.onclick = function() { alert(this.innerHTML); }
Updated Fiddle
Another simple solution is to have a wrapper function.
for (var i = 0; i < 5; ++i) {
(function(i){
var listNode = document.createElement("li");
listNode.innerHTML = i;
listNode.onclick = function() { alert(i); }
recipeUL.appendChild(listNode);
})(i);
}
This way you can always rely on the iterator variable i.
This is where closure concept plays its role. Try modifying listNode.onclick = function() { alert(i); } line of code with this:
listNode.onclick = (function(index) { return function(){alert(index);} })(i);
I have updated your fiddle..
FIDDLE

javascript: localise variables in block [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 9 years ago.
I try to understand variable scope in Javascript. Here is what I am doing:
<script>
for (var i = 0; i < 3; i++) {
document.getElementById(i).onclick = function() {
console.log(i);
}
}
</script>
The output is always 3, and I understand that's because i has been retained by reference. How do I localise i so it can log incremented value?
Thanks!
update
Thanks guys for quick and decent responses. the solutions are indeed of help!
Initially, I was trying a similar approach to #GrailsGuy, here it is:
<script>
for (var i = 1; i <= 3; i++) {
document.getElementById(i).onclick = function() {
console.log(logCall(i));
}
}
function logCall(i) {
return i;
}
</script>
But it looks like i isn't being localised. Cannot figure out why!
Create a new scope
for (var i = 0; i < 3; i++) {
(function(i) {
document.getElementById(i).onclick = function() {
console.log(i);
}
}(i));
}
In Javascript, scope is not created by brackets (contrary to the C-like syntax). Scope is however, created in functions, so one way is to extract the call into a function (updated):
<script>
for (var i = 0; i < 3; i++) {
logCall(i);
}
function logCall(i) {
document.getElementById(i).onclick = function() {
console.log(i);
}
}
</script>
This has the added benefit of making the code a bit more reusable.

Categories

Resources