Caching functions in an Array - javascript

In a discussion at Passing 'this' and argument to addEventListener function without using bind, caching functions was mentioned.
For example, considering the following Event Listeners:
window.addEventListener('event1', callback1, false);
window.addEventListener('event2', callback2, false);
window.addEventListener('event3', callback3, false);
window.addEventListener('event4', callback4, false);
window.addEventListener('event5', callback5, false);
Can their removal be cached (for example in an array)?
var unloaders = []; // Keeps track of unloader functions
unloaders.push(window.removeEventListener('event1', callback1, false));
unloaders.push(window.removeEventListener('event2', callback2, false));
unloaders.push(window.removeEventListener('event3', callback3, false));
unloaders.push(window.removeEventListener('event4', callback4, false));
unloaders.push(window.removeEventListener('event5', callback5, false));
Finally, if they can be cached, how can they be executed at the correct time?
for (let i = 0, len = unloaders.length; i < len; i++) {
//
}

unloaders.push(window.removeEventListener('event1', callback1, false)) will not put the function in the array to be executed later, but execute the function and put the result value into the array, i.e. not what you want.
The unload from the other question will actually construct an anonymous closure function and put it into the array, so simplified:
var unloaders = []; // Keeps track of unloader functions
unloaders.push(function() {
window.removeEventListener('event2', callback2, false);
});
This is somewhat analog to binding the function and putting the bound function into the array, so the following would yield the same result:
// This just binds the function, effectively creating a new function,
// but does not execute it (yet)
var bound = window.removeEventListener.bind(window, 'event2', callback2, false);
unloaders.push(bound);
I like the first style better, but both are OK and actually not having a closure but a bound function might avoid some issues in some circumstances where the closure closes over too much stuff keeping it artificially alive. But that is usually a rare occurrence.
Anyway to finally call the functions stored in the array, you'd just have to iterate over it and then call the functions one after another.
for (let i = 0, len = unloaders.length; i < len; i++) {
unloaders[i]();
}
But, to avoid that an exceptions exit the loop early, I suggest you wrap the calls in a try-catch.
for (let i = 0, len = unloaders.length; i < len; i++) {
try {
unloaders[i]();
}
catch (ex) {
// Do something
}
}
Actually, it might be preferable to call unloaders in the reverse order (last-in, first-out).
for (let i = unloaders.length - 1; i >= 0; i--) {
try {
unloaders[i]();
}
catch (ex) {
// Do something
}
}
The unload function from the other question has some more magic in it: It returns a function that lets you call the unloader you just registered at any time, properly removing it from the unloaders array when doing so. This is important for the unloadWindow function I also provided.

Related

setInterval calling function with an undefined parameter

This question has been flagged as already answered with a link provided above. However, I already read that answer and it only answered how to use setInterval in a for loop. There were no functions being called with parameters passed to them in that solution, and that is my situation, so I couldn't use it to fix my situation.
I'm fairly new to programming, so I'll try to describe as best as I can. In setInterval, I am passing a parameter to the function toggleClusters which setInterval calls. The debugger shows the parameter as being correct. It is a reference to an array position that holds an object literal that contains map marker objects. I seem to be misunderstanding something about what values stay around and what do not when using setInterval, because the debugger shows the correct object literal being passed as an arg, but when the function is called, the debugger shows the obj that is supposed to be passed as undefined. Is it that this passed value no longer exists when the function is called?
function setClusterAnimations() {
for (var i = 0; i < clusters.length; i++) {
//intervalNames stores handle references for stopping any setInterval instances created
intervalNames.push(setInterval(function () {
//clusters[i] will hold an object literal containing marker objects
toggleClusters(clusters[i]);
}, 1000));
}
}
//cObj is coming back as undefined in debugger and bombing
function toggleClusters(cObj) {
var propCount = Object.keys(cObj).length;
for (var prop in cObj){
if (prop.getZIndex() < 200 || prop.getZIndex() == 200 + propCount) {
prop.setZIndex(200);
}
else {
prop.setZindex(prop.getZIndex() + 1)
}
}
}
This is typically the issue with such asynchronous calls as with setInterval(). You can solve this in different ways, one of which is using bind():
for (var i = 0; i < clusters.length; i++) {
//intervalNames stores handle references for stopping any setInterval instances created
intervalNames.push(setInterval(function (i) {
//clusters[i] will hold an object literal containing marker objects
toggleClusters(clusters[i]);
}.bind(null, i), 1000));
}
The toggleClusters(clusters[i]) statement will only be executed when your loop has finished, at which time i will be beyond the correct range (it will be clusters.length). With bind(), and mostly with the function parameter i, you create a separate variable in the scope of the call back function, which gets its value defined at the moment you execute bind(). That i is independent from the original i, and retains the value you have given it via bind().
that is because your "i" variable is not captured in the function passed as an argument to setInverval.
Therefore , when this function is invoked, i is always equal to clusters.length.
consider the differences between the two following pieces of code:
var arr = [1, 2, 3];
var broken = function() {
for(var i = 0; i < arr.length; ++i) {
setInterval(function() {
console.log("broken: " + arr[i]);
}, 1000);
// logs broken: undefined
}
};
var fixed = function() {
for(var i = 0; i < arr.length; ++i) {
setInterval((function(k) {
return function() {
console.log("fixed: " + arr[k]);
}
}(i)), 1000); // i is captured here
}
};

JavaScript Function inside the loop

Can someone explain to me why JSLint complains about "Function inside the loop" with this example:
for (var i = 0; i < buttons.length; i++) {
(function(i) {
buttons[i].onclick = function(e) {
t.progressBars[t.current].update(buttons[i].getAttribute("data-value"));
}
})(i);
}
But dosen't when I change it to:
function makeHandler(i)
{
return function() {
t.progressBars[t.current].update(buttons[i].getAttribute("data-value"));
}
}
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = makeHandler(i);
}
I don't quite understand as it seems that with each loop iteration new function object has to be returned, even though it happens inside of makeHandler() function. Why the second example is ok with JS linters?
Quoting from linterrors,
var elems = document.getElementsByClassName("myClass"), i;
for (i = 0; i < elems.length; i++) {
(function (iCopy) {
"use strict";
elems[i].addEventListener("click", function () {
this.innerHTML = iCopy;
});
}(i));
}
What we have now captures the value of i at each iteration of the loop. This happens because JavaScript passes arguments to functions by value. This means that iCopy within the capturing function is not related to i in any way (except for the fact that they happen to have the same value at that point in time). If i changes later (which it does - on the next iteration of the loop) then iCopy is not affected.
This will work as we expect it to but the problem now is that the JavaScript interpreter will create an instance of the capturing function per loop iteration. It has to do this because it doesn't know if the function object will be modified elsewhere. Since functions are standard JavaScript objects, they can have properties like any other object, which could be changed in the loop. Thus by creating the function in the loop context, you cause the interpreter to create multiple function instances, which can cause unexpected behavior and performance problems. To fix the issue, we need to move the function out of the loop:
I would have liked to use Array.prototype.forEach here, like this
buttons.forEach(function(curButton) {
curButton.onclick = function(e) {
t.progressBars[t.current].update(curButton.getAttribute("data-value"));
};
});
Your two examples are not equivalent.
In the first, you are creating an anonymous function and calling it on every loop.
The inner function (the click event handler) is fine - you're assigning a new function - but it's the anonymous outer function that is inefficient in this context. In your second example the outer function is refactored out of the loop where is it only created once, instead of buttons.length times.

Call function from an array asynchronously in JavaScript [duplicate]

This question already has an answer here:
Why is the loop assigning a reference of the last index element to? [duplicate]
(1 answer)
Closed 8 years ago.
I've created a simple observer model in a JavaScript WebApp to handle event-listeners on a more complex JS-Object model (no DOM events). One can register event listener functions that are then stored in an array. By calling a member function out of the wider application of the model the event listeners are executed. So far so good. Here's the implementation that works well:
var ModelObserver = function() {
this.locationObserverList = [];
}
ModelObserver.prototype.emitEvent = function(eventtype, data) {
for(var i=0; i < this.locationObserverList.length; i++) {
var fns = this.locationObserverList[i];
fns(data); // function is being called
}
};
ModelObserver.prototype.registerLocationListener = function( fn) {
this.locationObserverList.push(fn);
};
If tested it with two listeners in a small sample html site, all good.
Now I want to make the call to the function asynchronously. I tried to change the code of the respective function as follows:
ModelObserver.prototype.emitEvent = function(eventtype, data) {
for(var i=0; i < this.locationObserverList.length; i++) {
var fns = this.locationObserverList[i];
setTimeout(function() {fns(data);}, 0);
}
};
Unfortunately I have a problem here: only the second listener is being called, but now twice. It seems to be a conflict with the fns variable, so I tried this:
ModelObserver.prototype.emitEvent = function(eventtype, data) {
var fns = this.locationObserverList;
for(var i=0; i < this.locationObserverList.length; i++) {
setTimeout(function() {fns[i](data);}, 0);
}
};
Now I get an error: "Uncaught TypeError: Property '2' of object [object Array] is not a function".
Does anyone have an idea how to get this working asynchronously?
The anonymous function you're giving setTimeout has an enduring reference to the variables it closes over, not a copy of them as of when it was created.
You need to make it close over something else. Usually, you use a function that builds the function for setTimeout and closes over args to the builder:
ModelObserver.prototype.emitEvent = function(eventtype, data) {
for(var i=0; i < this.locationObserverList.length; i++) {
var fns = this.locationObserverList[i];
setTimeout(buildHandler(fns, data), 0);
// Or combining those two lines:
//setTimeout(buildHandler(this.locationObserverList[i], data), 0);
}
};
function buildHandler(func, arg) {
return function() {
func(arg);
};
}
There, we call buildHandler with a reference to the function and the argument we want it to receive, and buildHandler returns a function that, when called, will call that function with that argument. We pass that returned function into setTimeout.
You can also do this with ES5's Function#bind, if you're in an ES5 environment (or include an appropriate shim, as this is shimmable):
ModelObserver.prototype.emitEvent = function(eventtype, data) {
for(var i=0; i < this.locationObserverList.length; i++) {
var fns = this.locationObserverList[i];
setTimeout(fns.bind(undefined, data), 0);
// Or combining those two lines:
//setTimeout(this.locationObserverList[i].bind(undefined, data), 0);
}
};
Skipping some details, that basically does what buildHandler above does.
More on this (on my blog): Closures are not complicated
Side note: By scheduling these functions to be called later via setTimeout, I don't think you can rely on them being called in order. That is, even if you schedule 1, 2, and 3, I don't know that you can rely on them being called that way. The (newish) spec for this refers to a "list" of timers, suggesting order, and so one might be tempted to think that registering timers in a particular order with the same timeout would have them execute in that order. But I don't (skimming) see anything in the spec guaranteeing that, so I wouldn't want to rely on it. A very quick and dirty test suggested the implementations I tried it on did that, but it's not something I'd rely on.
ModelObserver.prototype.emitEvent = function(eventtype, data) {
var fns = this.locationObserverList;
for(var i=0; i < this.locationObserverList.length; i++) {
(function(j){
setTimeout(function() {fns[i](data);}, 0);
}(i));
}
};
Try this
The second try is not going to work. In your first sample try -
setTimeout(function() {this.fns(data);}, 0);

How to pass a variable to a function that's part of the parameters of a function call?

for (var i = 0; i < json.length; i++) {
$.Mustache.load('/mustaches.php', function(i) {
//Do Something
});
}
How do I pass the var i to the function in this case?
EDIT: Sorry I don't actually want to make the Mustache.load call too many times. Only once. How can I do that?
This is a little more complicated than you might think, as you must ensure you pass the right value of i, so that the callback doesn't use the value of end of loop.
for (var i = 0; i < json.length; i++) {
(function(i){
$.Mustache.load('/mustaches.php', function() {
// use i. Call a function if necessary
//Do Something
});
})(i);
}
About the callback term : it refers to a function you pass as argument so that the function you call can call it back.
To understand the code I wrote, you must
understand that the callback is called later, when the loop has finished and so when i in the loop has the value of end of loop
that the scope of a non global variable is the function call in which it is defined. That's why there's this intermediate function : to define another variable i which is called with the value of the loop
An elegant way to solve your question would be using the bind method.
for (var i = 0; i < json.length; i++) {
$.Mustache.load('/mustaches.php', function(i) {
//Do Something
}.bind(this, i));
}
the bind method returns a new function with a new context (in this case this) and applies one (or more) argument(s) to your function (i in this particular case). You can find more about bind and currying here.
EDIT. You can optimise your loop by loading the template only once. In fact, $.Mustache.load fetches /mustache.php on each cycle of the loop. Also, because the function asynchronously fetches the template with AJAX, you might get not consistent ordering in your template (one response may take longer than others). The fix is pretty straightforward: we load the template and then we iterate through the loop.
$.get('/mustache.php').done(function(template){
$.Mustache.add('my-template', template);
for (var i = 0, len = json.length; i < len; ++i) {
var rendered_template = $.Mustache.render('my-template', {
i: i,
...
});
}
});

Javascript Closure Problem

I know this kind of question gets asked alot, but I still haven't been able to find a way to make this work correctly.
The code:
function doStuff () {
for (var i = 0; i< elementsList.length; i++) {
elementsList[i].previousSibling.lastChild.addEventListener("click", function(){
toggle(elementsList[i])}, false);
}
} // ends function
function toggle (element) {
alert (element);
}
The problem is in passing variables to the toggle function. It works with the this keyword (but that sends a reference to the clicked item, which in this case is useless), but not with elementsList[i] which alerts as undefined in Firefox.
As I understood it, using anonymous functions to call a function is enough to deal with closure problems, so what have I missed?
Try:
function startOfFunction() {
for (var i = 0; i< elementsList.length; i++) {
elementsList[i].previousSibling.lastChild.addEventListener(
"click",
(function(el){return function(){toggle(el);};})(elementsList[i]),
false
);
}
} // ends function
function toggle (element) {
alert (element);
}
The Problem is, that you want to use the var i! i is available in the onClick Event, (since closure and stuff). Since you have a loop, i is counted up. Now, if you click on any of the elements, i will always be elementsList.length (since all event functions access the same i )!
using the solution of Matt will work.
As an explanation: the anonymous function you use in the for loop references the variable "i" to get the element to toggle. As anonymous functions use the "live" value of the variable, when somebody clicks the element, "i" will always be elementsList.length+1.
The code example from Matt solves this by sticking the i into another function in which it is "fixated". This always holds true:
If you iterate over elements attaching events, do not use simple anonymous functions as they screw up, but rather create a new function for each element. The more readable version of Matts answer would be:
function iterate () {
for (var i = 0; i < list.length; i++) {
// In here, i changes, so list[i] changes all the time, too. Pass it on!
list[i].addEventListener(createEventFunction(list[i]);
}
}
function createEventFunction (item) {
// In here, item is fixed as it is passed as a function parameter.
return function (event) {
alert(item);
};
}
Try:
function doStuff () {
for (var i = 0; i< elementsList.length; i++) {
(function(x) {
elementsList[x].previousSibling.lastChild.addEventListener("click", function(){
toggle(elementsList[x])}, false);
})(i);
}
} // ends function
I think it might be an issue with passing elementsList[i] around, so the above code has a closure which should help.

Categories