Confusion with lambda function parameter - javascript

I'm trying to use setTimeout with a lambda function in a for loop, but it only captures the last parameters for the contents of the lambda function from the final iteration of the for loop. In C# we can make a a new variable to pass as the parameter each time it is passed into a new lambda function, but that doesn't appear to work in javascript. Any clues?
The specific function I'm talking about is setElementsByIdTimed()
var gElems = new Array();
document.addEventListener("DOMContentLoaded", function (event) {
//setElementsById('icon_anim_start' , "icon_anim_end");
//setTimeout(function() {setElementsById('icon_anim_end' , "icon_anim");} , 500);
var delay = setElementsByIdTimed('icon_anim_start' , "icon_anim_end" , 250);
setTimeout(function() {setElementsById('icon_anim_end' , "icon_anim");} , delay);
});
function getElementsById (elementID){
var elementCollection = new Array();
var allElements = document.getElementsByTagName("*");
for(i = 0; i < allElements.length; i++){
if(allElements[i].id == elementID)
elementCollection.push(allElements[i]);
}
return elementCollection;
}
function setElementsById (elementID, newID) {
var elems = new Array();
elems = getElementsById(elementID);
for (var i = 0; i < elems.length; i++)
{
elems[i].id = newID;
}
}
function setElementsByIdTimed (elementID, newID , ms) {
var elems = new Array();
elems = getElementsById(elementID);
console.log("elems.length: " + elems.length);
gElems = elems;
for (var i = 0; i < elems.length; i++) {
var index = i
setTimeout(function() {
setElementId(index, newID);
}, ms * i);
}
return ms * (elems.length-1);
}
function setElementId (index , newID) {
console.log ("gElems.length: " + gElems.length + " index: " + index);
gElems[index].id = newID;
}
})

This is a classic JavaScript closure problem. Basically, there is only one instance of the index variable and it's declared outside the context of the lambda function. So every lambda function is using the same index, and they are executed after the loop completes so index looks to be out-of-bounds on every invocation.
To get this to work index must have closure scope:
function setElementsByIdTimed(elementID, newID , ms)
{
var elems = new Array();
elems = getElementsById(elementID);
console.log("elems.length: " + elems.length);
gElems = elems;
for(var i = 0; i < elems.length; i++)
{
var index = i
setTimeout( setClosure(index,newID), ms * i);
}
return ms * (elems.length-1);
}
function setClosure( index, newID ) {
// this lambda is called after the timeout elapses
return function() {
setElementId(index, newID);}
}
You can also play a self-invocation trick, but it's a little mind-bendy:
function setElementsByIdTimed(elementID, newID , ms)
{
var elems = new Array();
elems = getElementsById(elementID);
console.log("elems.length: " + elems.length);
gElems = elems;
for(var i = 0; i < elems.length; i++)
{
var index = i
setTimeout( (function(idx,nid) {
return function () {
setElementId(idx,nid);}
})(index, newID),
ms * i);
}
return ms * (elems.length-1);
}
These are effectively the same solution, but the first syntax is probably a lot simpler to grasp.

instead of
for(var i = 0; i < elems.length; i++)
{
var index = i
setTimeout(function() {
setElementId(index, newID);
}, ms * i);
}
use an IIFE - https://developer.mozilla.org/en-US/docs/Glossary/IIFE
for(var i = 0; i < elems.length; i++)
{
(function(index) {
setTimeout(function() {
setElementId(index, newID);
}, ms * index);
}(i));
}

the problem was in
for(var i = 0; i < elems.length; i++)
{
var index = i
setTimeout(function() {
setElementId(index, newID);}, ms * i);
}
index is allways the same variable. Try:
for(var i = 0; i < elems.length; i++)
{
(function(index){
setTimeout(function() {
setElementId(index, newID);}, ms * i);
})(i);
}
each time you must access a variable within a clousure in a loop you must use a function to access it. You can also use a forEach applied to the array:
elems.forEach(function(index){
setTimeout(function() {
setElementId(index, newID);}, ms * i);
}
});

Related

I want to draw multiple image, but it works only for last image

This function is make "cards" array in target object, and I added some codes to draw each element in "cards" array(see the mark : this part), but it doesn't work. How can i do?
var player = {
cards = [];
};
function giveNCards(cardsArr, target, n) {
target.cards = [];
for (var i = 0; i < n; i++) {
target.cards.push(cardsArr.pop());
}
/////////// this part //////////
for (var i = 0; i < target.cards.length; i++) {
var cardImage = new Image();
cardImage.onload = (function(value) {
return function() {
ctx.drawImage(this, i * 100, 0);
}
})(i);
cardImage.src = "./images/" + target.cards[i] + ".png"
}
//////////////////////////////////////////
console.log(target.cards);
return target.cards;
}
cardImage is getting overwritten on each loop.
Variables (when defined with the 'var' keyword) are local to the function (function scope) and get 'hoisted' (moved to the top). What does that mean? Basically, your code will run like this:
function giveNCards(cardsArr, target, n) {
var i, cardImage // <-- cardImage defined first here (hoisted)
target.cards = [];
for (i = 0; i < n; i++) {
target.cards.push(cardsArr.pop());
}
for (i = 0; i < target.cards.length; i++ ){
cardImage = new Image();
cardImage.onload = (function(value){
return function(){
ctx.drawImage(this, i * 100, 0);
}
})(i);
cardImage.src = "./images/" + target.cards[i] + ".png"
}
console.log(target.cards);
return target.cards;
}
The simplest fix for this (though there are others) is to move the contents of your second for loop to another function which will give it it's own scope.
function giveNCards(cardsArr, target, n) {
var i
target.cards = [];
for (i = 0; i < n; i++) {
target.cards.push(cardsArr.pop());
}
for (i = 0; i < target.cards.length; i++ ){
renderCard(target, i)
}
console.log(target.cards);
return target.cards;
}
function renderCard(target, i) {
var cardImage = new Image();
cardImage.onload = (function(value){
return function(){
ctx.drawImage(this, i * 100, 0);
}
})(i);
cardImage.src = "./images/" + target.cards[i] + ".png"
}
Pay attention, the i value that you used inside the IFFE is from the global scope, therefore you always modifies the same value
var player = {
cards = [];
};
function giveNCards(cardsArr, target, n) {
target.cards = [];
for (var i = 0; i < n; i++) {
target.cards.push(cardsArr.pop());
}
/////////// this part //////////
for ( var i = 0; i < target.cards.length; i++ ){
var cardImage = new Image();
cardImage.onload = (function(value){
return function(){
ctx.drawImage(this, value * 100, 0);
// -------------------^
}
})(i);
cardImage.src = "./images/" + target.cards[i] + ".png"
}
//////////////////////////////////////////
console.log(target.cards);
return target.cards;
}

Cannot read property 'addEventListener' of undefined

function assign(id){
return document.getElementById(id) ;
}
var b = ['p','q','r','s','t','u','v'];
var a = ['fname','lname','email','password','r_password','g_m',"g_f"] ;
for (i=0;i<a.length;i++) {
var x = {} ;
x[b[i]] = assign(a[i]) ;
x[i].addEventListener('click', function() { alert(x[i].value) ;} ,false) ;
}
I want just array of variables and IDs assign with them in for loop .
You're expecting x[i] to return the DOM element you just stored, but you're storing the DOM element at x[b[i]], not x[i]. Be consistent, and that error will go away.
The code still won't work, though, because you're falling into the closure trap: When the click occurs, the event handler will use i as it is then, not as it was when the handler was created. So all the handlers will see i as a.length and fail.
I usually use a builder function to handle that:
for (i = 0; i < a.length; i++) {
var x = {};
x[b[i]] = asign(a[i]);
hookUp(i);
}
function hookUp(index) {
x[b[index]].addEventListener('click', function() {
alert(x[b[index]].value);
}, false);
}
hookUp uses index (which we don't change) rather than i. (I also took a guess at which to use, x[i] or x[b[i]].)
That said, if your goal is to alert the value of the element that was clicked, use this instead:
for (i = 0; i < a.length; i++) {
var x = {};
x[b[i]] = asign(a[i]);
x[b[i]].addEventListener('click', function() {
alert(this.value);
}, false);
}
or Event#currentTarget:
for (i = 0; i < a.length; i++) {
var x = {};
x[b[i]] = asign(a[i]);
x[b[i]].addEventListener('click', function(e) {
alert(e.currentTarget.value);
}, false);
}
function asign(id){
return document.getElementById(id) ;
}
var a = ['a','b','c'];
var b = ['fname','lname','email'] ;
for (i=0;i<a.length;i++) {
(function(i){
var x = {} ;
x[b[i]] = asign(a[i]) ;
x[b[i]].addEventListener('click', function() { alert(i+1) ;} ,false) ;
})(i);
}
<div id="a">Div 1</div>
<div id="b">Div 2</div>
<div id="c">Div 3</div>

Angular – Simple Typewriter Effect?

There is a beautiful typewriter directive already written, but I'm looking for something more simple, that just adds each letter after an interval. I can't get this to work. It shows the text all at once. Something is wrong with the $timeout. Does anyone have any suggestions?
var time = 1000;
var addLetter = function(i) {
$scope.string2 = $scope.string.substr(0, i);
};
for (var i = 0, len = $scope.string.length; i < len; i++) {
(function(time) {
$timeout(function() {
addLetter(i);
}, (time + 300));
})(i);
}
Here is an alternate way.
var content = "contentcontentcontentcontentcontentcontentcontentcontentcontentcontentcontent";
$scope.type = "";
var i=0;
var timer = $interval(function(){
if(i<content.length)
$scope.type += content[i];
else
$interval.cancel(timer);
i++;
$scope.$apply();
}, 100);
Credit: https://gist.github.com/frozonfreak/8018689
Demo: http://plnkr.co/edit/BILaWVuNpao2zcIInXLl?p=preview
Update - This is working for me, not sure if it could be more efficient...
var time = 0;
var addLetter = function(i) {
$scope.string2 = $scope.string.substr(0, i);
};
for (var i = 0, len = $scope.string.length; i < len + 1; i++) {
time = time + 50;
(function(time, i) {
$timeout(function() {
addLetter(i);
}, (time));
})(time, i);
}

setTimeout in a loop and pass variables to use later

Consider the following code:
function func() {
var totalWidths = 0;
for( var i = 0, count = arr.length; i < count; i++ ) {
var image = arr[i];
insertElemInDOM(image);
preloadImage(image,function(){
var w = image.width();
totalWidths += w;
});
}
// do something with the variable "totalWidths"
doSomething(totalWidths)
}
I have 2 problems here. The image will be always the same (first problem), which one can solve with an anonymous function:
for(...) {
(function(image) {
preload(image,function() {
// now image is the correct one
});
})(image);
}
But how do I manage the totalWidths variable in order to use it later on doSomething(totalWidths)? The previous code would have a value of 0 for totalWidths.
Thanks!
You could timeout the whole loop and the doSomething, that's much more performant than setting up so many timeouts:
setTimeout(function() {
var inc = 0;
for (var i = 0; i < count; i++) {
var w = arr[i].width();
inc++;
}
doSomething(inc);
}, 1000);
However, what you actually seem to want are nested timeouts, i.e. waiting 1s for each iteration step and doing something after all have finished:
var inc = 0, count;
function asyncLoop(i, callback) {
if (i < count) {
var w = arr[i].width();
inc++;
setTimeout(function() {
asyncLoop(i+1, callback);
}, 1000);
} else {
callback();
}
}
asyncLoop(0, function() {
doSomething(inc);
});
OK, now that we know what you need the solution is to check after each load event whether all images are loaded:
var totalWidths = 0,
count = arr.length,
loaded = 0;
for (var i = 0; i < count; i++)
(function (image) {
insertElemInDOM(image);
preload(image, function() {
totalWidths += image.width();
// counter:
loaded++;
if (loaded == count-1) // the expected value
doSomething(totalWidths); // call back
});
})(arr[i]);

What is the correct way to fix this Javascript closure

I have been familiarizing myself with javascript closures and ran across this article
http://blog.morrisjohns.com/javascript_closures_for_dummies.html
Due to the closure, Example 5 does not work as expected. How would one modify
result.push( function() {alert(item + ' ' + list[i])} );
to make the code work?
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( function() {alert(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// using j only to help prevent confusion - could use i
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList();
Thanks!
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
(function(i) {
var item = 'item' + list[i];
result.push(function() {
alert(item + ' ' + list[i]);
});
})(i);
}
return result;
}
You need the i and item from the different functions to be separate variables in different scopes (so they can have different values instead of being shared). Since Javascript only has function scope you need to create a wrapper function to contain these variables
function buildList(list) {
var result = [];
function make_f(item, i){
//item and i are now private variables of make_f
//and wont be shared by the returned closure
// the other variable in scope (list) is not shadowed so
// it is still shared but that is not a problem since we
// never change its value.
return function() { alert(item + ' ' + list[i]) };
}
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( make_f(item, i) );
}
return result;
}
You can also do the same thing with an anonymous function that is immediately invoked (the (function(){}()) pattern).
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( (function (item, i){
return function(){function() {alert(item + ' ' + list[i])};
})(item, i) );
}
return result;
}
Put it in... another closure!
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
result.push((function(item, listItem){
return function() {
alert(item + ' ' + listItem);
};
})('item' + list[i], list[i]));
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// using j only to help prevent confusion - could use i
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList();
var has a function scope but if you use let then you will get block scope.
So I would do:
for (let i = 0; i < list.length; i++) and let item...

Categories