Variable used as function argument gets changed, affecting the later function call - javascript

I have no idea what is going on, I have spent countless hours debugging my code until I found the weak spot.
Concept
function fnc() {
var hl = { // hl stands for hyperlink (<a>)
uid : ["player.id", "uid"],
inVal : ["infra.value*", "infrastructure_value"]
}
for (key in hl) { .. do the code in block 1 at bottom .. }
}
The code should go through the array (object) of values consisting of id : [text, call] and bind a click(fnc(call)) event to an <a>text</a> with given id.
(Clicking the link launches the same function - it is desired.)
The Code
This is the code in short:
fnc = function(sort_by, direction) {
var qswitch = 1;
if (qswitch == 1) {
key = "uid"; console.log("#"+key); // #uid
$("#"+key).unbind("click").click( function() { fnc(hl[key][1], direction); });
console.log(key); // uid
key = "inVal"; console.log("#"+key); // #inVal
$("#"+key).unbind("click").click( function() { fnc(hl[key][1], direction); });
console.log(key); // inVal
}
if (qswitch == 2) {
$("#uid").unbind("click").click( function() { fnc(hl["uid"][1], direction); });
$("#inVal").unbind("click").click( function() { fnc(hl["inVal"][1], direction); });
}
}
The problem
You can see two blocks of the same code. The first is supposed to work dynamically as described above, but both links refer to the same function call. (<-this is the error) Binding the events without using a variable works just fine and each link fires it's own function(parameters).
Is it a bug?
..or am I just dumb and overworked?
Install the tampermonkey userscript, visit any site, open console (F12) and watch the output. Whenever you click a link, it writes what function(parameters) it calls. Each of the two links should have different function call. I suggest using this blank site.
theCode.monkey.js

key = "uid"; console.log("#"+key); // #uid
$("#"+key).unbind("click").click( function() { fnc(hl[key][1], direction); });
console.log(key); // uid
key = "inVal"; console.log("#"+key); // #inVal
$("#"+key).unbind("click").click( function() { fnc(hl[key][2], direction); });
console.log(key); // inVal
The problem here is that key is overwritten by the second assignment before it's used in the first callback. Event callbacks are not called at the same time as their defining function and their defining function does not wait for them to be run. The easiest solution would be to use two separate variables, but since it looks like I'm looking at an unrolled loop and your code actually looks like this:
for(var key in hl){
//compute i
key = "inVal"; console.log("#"+key); // #inVal
$("#"+key).unbind("click").click( function() { fnc(hl[key][i], direction); });
console.log(key); // inVal
}
(if var is missing, you should add it - undeclared variables reside in the global scope. You don't want them there. In strict mode, they fail completely) (not sure where i comes from, but let's assume it's generated in each loop before what is seen) you can use an immediately invoked function expression (IIFE) to capture the value into a new variable in a new scope (remember, javascript uses function scopes):
for(var key in hl){
// compute `i`
(function(key, i){
key = "inVal"; console.log("#"+key); // #inVal
$("#"+key).unbind("click").click( function() { fnc(hl[key][i], direction); });
console.log(key); // inVal
})(key,i)
}
now key and i inside the loop refer to the IIFE arguments, not to the constantly changing variable variables declared outside the loop.
Also note that: if previous values of i are not used to generate new ones, then the computation of i can be moved into the function body. If they are used - please note that the order of object keys is not guaranteed.

Related

Returning a value inside an if clause in javascript

_editor: function () {
//retrieve all the editors on the current page
var editors = window.tinymce.editors;
var container = this.element;
//pick one that's associated with current container
$(editors).each(function (i, ed) {
if (ed.id == container.id) {
return ed; // even if this is invoked,
}
});
// undefined is returned
}
I had to change the above code to
_editor: function () {
//retrieve all the editors on the current page
var editors = window.tinymce.editors;
var container = this.element;
var editor;
$(editors).each(function (i, ed) {
if (ed.id == container.id) {
editor = ed; // do not return yet. Store it.
}
});
return editor; // return here
}
I assume this is because of JavaScript's scope characteristics. Could someone explain 1) if this is only inherent in JavaScript 2) what exactly is going on in each functional scope in the above code?
Thank you.
In the first case, you are returning a value from that anonymous function passed to $(editors).each, not the outer function. In the second case you are returning from the outer function.
This is how it works with pretty much any language that allows nested functions. return only returns from the innermost function.
The issue is that you have nested functions. You have the function assigned to the _editor property, and within that you have a function that's being invoked by $.each(). The return statement returns from the closest containing function, so in the first example it's returning from the $.each() iteration function, not the _editor function.
$.each() uses the return value of the iteration function to determine whether to continue looping -- if the function returns false, it stops at that element (similar to using the break; statement in a for or while loop).
Could someone explain 1) if this is only inherent in JavaScript 2) what exactly is going on in each functional scope in the above code?
The code is returning from the function passed to .each(), so it doesn't impact the enclosing function.
You can use $.grep for a cleaner solution.
_editor: function () {
//retrieve all the editors on the current page
var editors = window.tinymce.editors;
var container = this.element;
return $.grep(editors, function (ed, i) {
return ed.id == container.id;
})[0];
}
This is basically a filter. The result will be the items in the collection where you returned a truthy value. And so we just return the first truthy result (index 0 of the result).
It returns from the function called by each:
$(editors).each(function (i, ed) { // <---
if (ed.id == container.id) { |
return ed; // <--- this exits this --
is [this] only inherent in JavaScript[?]
No, many languages which use anonymous functions, also called lambdas, operate like this. A couple of examples are C# and ruby. Calling return exits themselves, rather than the functions they are invoked in.
what exactly is going on in each functional scope in the above code?
$(editors).each(function (i, ed) {
if (ed.id == container.id) {
editor = ed; // do not return yet. Store it.
}
});
The function body is called once for each element ed in $(editors). When the loop exits, the last value for which ed.id == container.id is then stored in editor. The second argument i is the index (0,1,2,3,...) incremented in each iteration.

Javascript Function Scoped For Loops

Here's an example of a situation where a simple JS loop does not behave as expected, because of the loop variable not being in a separate scope.
The solution often presented is to construct an unpleasant-looking bit of loop code that looks like this:
for (var i in obj) {
(function() {
... obj[i] ...
// this new shadowed i here is now no longer getting changed by for loop
})(i);
}
My question is, could this be improved upon? Could I use this:
Object.prototype.each = function (f) {
for (var i in this) {
f(i,this[i]);
}
};
// leading to this somewhat more straightforward invocation
obj.each(
function(i,v) {
... v ...
// alternatively, v is identical to
... obj[i] ...
}
);
when I ascertain that I need a "scoped loop"? It is somewhat cleaner looking and should have similar performance to the regular for-loop (since it uses it the same way).
Update: It seems that doing things with Object.prototype is a huge no-no because it breaks pretty much everything.
Here is a less intrusive implementation:
function each (obj,f) {
for (var i in obj) {
f(i,obj[i]);
}
}
The invocation changes very slightly to
each(obj,
function(i,v) {
... v ...
}
);
So I guess I've answered my own question, if jQuery does it this way, can't really go wrong. Any issues I've overlooked though would warrant an answer.
Your answer pretty much covers it, but I think a change in your original loop is worth noting as it makes it reasonable to use a normal for loop when the each() function isn't handy, for whatever reason.
Update: Changed to use an example that's similar to the example referenced by the question to compare the different approaches. The example had to be adjusted because the each() function requires a populated array to iterate over.
Assuming the following setup:
var vals = ['a', 'b', 'c', 'd'],
max = vals.length,
closures = [],
i;
Using the example from the question, the original loop ends up creating 2n functions (where n is the number of iterations) because two functions are created during each iteration:
for (i = 0; i < max; i++) {
closures[i] = (function(idx, val) { // 1st - factoryFn - captures the values as arguments
return function() { // 2nd - alertFn - uses the arguments instead
alert(idx + ' -> ' + val); // of the variables
};
})(i, vals[i]);
}
This can be reduced to creating only n + 1 functions by creating the factory function once, before the loop is started, and then reusing it:
var factoryFn = function(idx, val) {
return function() {
alert(idx + ' -> ' + val);
};
};
for (i = 0; i < max; i++) {
closures[i] = factoryFn(i, vals[i]);
}
This is nearly equivalent to how the each() function might be used in this situation, which would also result in a total of n + 1 functions created. The factory function is created once and passed immediately as an argument to each().
each(vals, function(idx, val) {
closures[idx] = function() {
alert(idx + ' -> ' + val);
};
});
FWIW, I think a benefit to using each() is the code is a bit shorter and creating the factory function right as it's passed into the each() function clearly illustrates this is its only use. A benefit of the for loop version, IMO, is the code that does the loop is right there so it's nature and behavior is completely transparent while the each() function might be defined in a different file, written by someone else, etc.
Global Scope
When something is global means that it is accessible from anywhere in your code. Take this for example:
var monkey = "Gorilla";
function greetVisitor () {
return alert("Hello dear blog reader!");
}
If that code was being run in a web browser, the function scope would be window, thus making it
available to everything running in that web browser window.
Local Scope
As opposed to the global scope, the local scope is when something is just defined and accessible in a
certain part of the code, like a function. For instance;
function talkDirty () {
var saying = "Oh, you little VB lover, you";
return alert(saying);
}
alert(saying); // Throws an error
If you take a look at the code above, the variable saying is only available within the talkDirty
function. Outside of it it isn’t defined at all. Note of caution: if you were to declare saying without
the var keyword preceding it, it would automatically become a global variable.
What this also means is that if you have nested functions, the inner function will have access to the
containing functions variables and functions:
function saveName (firstName) {
function capitalizeName () {
return firstName.toUpperCase();
}
var capitalized = capitalizeName();
return capitalized;
}
alert(saveName("Robert")); // Returns "ROBERT"
As you just saw, the inner function capitalizeName didn’t need any parameter sent in, but had complete
access to the parameter firstName in the outer saveName function. For clarity, let’s take another
example:
function siblings () {
var siblings = ["John", "Liza", "Peter"];
function siblingCount () {
var siblingsLength = siblings.length;
return siblingsLength;
}
function joinSiblingNames () {
return "I have " + siblingCount() + " siblings:\n\n" + siblings.join("\n");
}
return joinSiblingNames();
}
alert(siblings()); // Outputs "I have 3 siblings: John Liza Peter"
As you just saw, both inner functions have access to the siblings array in the containing function, and
each inner function have access to the other inner functions on the same level (in this case,
joinSiblingNames can access siblingCount). However, the variable siblingsLength in the siblingCount is
only available within that function, i.e. that scope.

How do I execute a function after the callbacks inside a for loop are completed?

I have a for loop in a search function, with a function that does a callback inside the loop, and I want to execute a BUILD() function after the loop, and after all the callbacks are completed. I am not sure how to do that, because the loop finishes before all the callbacks are done. The callbacks are API requests to get me data, and I want to BUILD() with that data.
I read up on deferred, so I tried to put the for loop inside a function to the deferred, and then calling BUILD() on '.then( ... )'. But that doesn't seem to work - I think I am understanding it wrong.
HELP?!
Note, this is using the Google Maps Places API (search and getDetails).
var types = {
'gym' : 'fitness, gym',
'grocery_or_supermarket': ''
}
function search() {
for (var key in types) {
var request = { ... };
service.search(request, searchCallback);
}
// PROBLEM AREA
BUILD();
}
function searchCallback(results, status) {
for (var i = 0; i < results.length; i++) {
var request = { ... };
service.getDetails(request, detailsCallback);
}
}
function detailsCallback(place, status) {
// add place marker to maps and assign info window and info window event
}
With a small modification of your code, it can be achieved.
var total = 1337; // Some number
var internal_counter = 0;
var fn_callback = function() {
searchCallback.apply(this, arguments);
if (++internal_counter === total) {
BUILD();
}
};
for (var i=0; i<total; i++) {
service.search(request, fn_callback);
...
Explanation
First, we create a local function and variable.
The variable is a counter, which is increased when the callback is called.
The function is passed to the asynchronous method (service.search), which calls the original callback. After increasing the counter, check the value of the counter against the variable which holds the total number of iterations. If these are equal, call the finishing function (BUILD).
A complex case: Dealing with nested callbacks.
var types = { '...' : ' ... ' };
function search() {
var keys = Object.keys(types);
var total = keys.length;
// This counter keeps track of the number of completely finished callbacks
// (search_callback has run AND all of its details_callbacks has run)
var internal_counter = 0;
for (var i=0; i<total; i++) {
var request = { '...' : ' ... ' };
services.search(request, fn_searchCallback);
}
// LOCAL Function declaration (which references `internal_counter`)
function fn_searchCallback(results, status) {
// Create a local counter for the callbacks
// I'm showing another way of using a counter: The opposite way
// Instead of counting the # of finished callbacks, count the number
// of *pending* processes. When this counter reaches zero, we're done.
var local_counter = results.length;
for (var i=0; i<results.length; i++) {
service.getDetails(request, fn_detailsCallback);
}
// Another LOCAL function (which references `local_counter`)
function fn_detailsCallback(result, status) {
// Run the function logic of detailsCallback (from the question)
// " ... add place marker to maps and assign info window ... "
// Reduce the counter of pending detailsCallback calls.
// If it's zero, all detailsCallbacks has run.
if (--local_counter === 0) {
// Increase the "completely finished" counter
// and check if we're finished.
if (++internal_counter === total) {
BUILD();
}
}
} // end of fn_detailsCallback
} // end of fn_searchCallback
}
The function logic is explained in the comments. I prefixed the heading of this section with "Complex", because the function makes use of nested local functions and variables. A visual explanation:
var types, BUILD;
function search
var keys, total, internal_counter, fn_searchCallback;
function fn_searchCallback
var result, status; // Declared in the formal arguments
var local_counter, i, fn_detailsCallback;
function fn_detailsCallback
var result, status; // Declared in the formal arguments
In the previous picture, each indention level means a new scope Explanaation on MDN.
When a function is called, say, 42 times, then 42 new local scopes are created, which share the same parent scope. Within a scope, declared variables are not visible to the parent scope. Though variables in the parent scope can be read and updated by variables in the "child" scope, provided that you don't declare a variable with the same name. This feature is used in my answer's function.
I think you understand this already, but as it is the BUILD() is getting called linearly while the previous callback functions are still running. It's like you've created extra threads. One way to solve the problem would be to make BUILD a callback from the search function with the for loop in it. This would guarantee all functionality is complete before calling it.
This question might help implement the callback: Create a custom callback in JavaScript

Javascript/ECMAScript Garbage collection

Consider the following code (you can just put this in the developer console in Chrome and check).
var obj = {
f: function () {
var myRef = this;
val = setTimeout(function () {
console.log("time down!");
myRef.f();
}, 1000);
}
};
If I then run
obj.f();
to start the timer, I can see every second "time down!"
If I then run
obj = null;
The timer still fires.
Just curious why doesn't garbage collection clear out the timer? The scary thing is that it appears that there is no way to delete the timer now - am I correct?
My guess is that technically window still holds a reference to the object still consequently the object stays in memory. I've experienced this problem in another ECMA based language (Actionscript) and built a library for handling it, but sort of thought Javascript would take a different approach.
obj is not garbage collected because the closure that you pass to setTimeout must be kept around in order to be executed. And it, in turn, holds a reference to obj because it captures myRef.
It would be the same if you passed that closure to any other function that kept it around (for example in an array).
There is no way to delete the timer now, without horrible hacks1. But this is pretty natural: it's an object's job to clean up after itself. This object's purpose is to infinitely fire a timeout, so that object clearly intends to never clean up after itself, which might be appropriate. You can't expect something to happen forever without using up at least some memory while it does so.
1 Horrible hack: since timer IDs are just integers, you can loop from, say, 1 to 1000000000 and call clearTimeout on each integer. Obviously this will kill other running timers!
In response to K2xL's comment.
A minor adjustment of your function and it does behave like you suggest. If obj is given a new value the if will fail, the propagation will stop, and the whole lot can be garbage collected:
var obj = {
f: function () {
var myRef = this;
if(myRef===obj){
val = setTimeout(function () {
console.log("time down!");
myRef.f();
}, 1000);
}
}
};
I'd prefer a slightly flatter structure, you can skip the object container and rely just on a standard closure:
(function(){
var marker={}
window.obj=marker
function iterator(){
if(window.obj===marker){
setTimeout(iterator,1000)
console.log("time down!")
}
}
iterator()
})()
Note that you can use any object you desire for marker, this could easily be a document element. Even if a new element with the same id is erected in its place the propagation will still stop when the element is removed from the document as the new element is not equal to the old one:
(function(){
var marker=document.getElementById("marker")
function iterator(){
if(document.getElementById("marker")===marker){
setTimeout(iterator,1000)
console.log("time down!")
}
}
iterator()
})()
List item
Of course the timer still fires; you're recursively calling it inside the nested function with myRef.f.
Your guess was that the window holds a reference to obj. That is true, however, that's not why setTimeout is recursively called, nor what can be done to cancel it.
There are a few ways to provide for timer clearing. One way would be to pass in a condition function in the beginning.
To stop the timer, merely call clearTimeout and then don't recursively call setTimeout. A basic example:
(Identifier val is created as a property of the global object. Always use var!)
var obj = {
f : function (i) {
// (GS) `this` is the base of `f` (aka obj).
var myRef = this;
var timer = setTimeout(function () {
if(i == 0) {
clearTimeout(timer);
return;
}
console.log(i, "time down!");
myRef.f(--i);
}, 1000);
}
};
obj.f(4);
Moving a step up from that, an isDone method can provide more featureful check with refs passed back an forth. The setTimeout can be changed to setInterval.
var obj = {
f : function (i, isDone, animEndHandler) {
var timer = setInterval(function() {
console.log(i, "time down!");
if(isDone(--i)) {
animEndHandler({toString: function(){return"blast off!"}, i: i});
clearInterval(timer);
}
}, 1000);
}
};
function isDone(i) {
return i == 0;
}
function animEndHandler(ev) {
console.log(""+ev);
}
obj.f(3, isDone, animEndHandler);
The garbage collector doesn't clear out the timer function because something in the implementation of setTimeout() maintains a reference to it until you call clearTimeout().
You are correct that if you do not clear it and drop the reference to the value returned by "setTimeout()" then you have introduced a "memory leak" (in that the timer function cannot be removed).

jQuery, hover method and closure

Have been struggling with Javascript closure for a while trying to wrap brain around function scopes, but I think they're wrapping around me instead. I've looked at a number of posts (Nyman's was the most helpful) but obviously still don't get it. Trying to run a loop over the hover method in jQuery. Need hover functions to ultimate trigger more than one action each, but would be happy to get them working with a single image swap each for now.
$(document).ready(function() {
imageSource = [];
imageSource[0] = 'images/img0.png' //load 0 position with "empty" png
imgArea = [];
for (var i=1; i<11; i++) {
(function( ){ //anonymous function for scope
imageSource[i] = 'images/img' + i + '.png';
imgArea[i] = '#areamap_Img' + i;
// running console.log here gives expected values for both
$(imgArea[i]).hover( //imgArea[i] (selector) works correctly here
function() {
$('#imgSwap').attr('src',imageSource[i]); // imageSource[i] is undefined here
},
function() {
$('#imgSwap').attr('src','images/img0.png');
});
})(); // end anonymous function and execute
}; // for loop
});
Tried the idea of using an anonymous function for scoping from another jQuery post. Seems to work OK but throws an undefined for the array value in the first hover function, I guess because it's an inside function (hardcoded image sources work correctly there).
There is indeed a problem with your closures, and it has to do with your usage of the var i. Since your anonymous function has no local version of i, it's using the version of the function above it. However, when it tries to access i at a later date, i == 11 (since that's what made the loop terminate). To fix this, you need to declare a local version of i in each anonymous function, like this:
for (var i=1; i<11; i++) {
(function( ){ //anonymous function for scope
var index = i; // The important part!
// It's not technically necessary to use 'index' here, but for good measure...
imageSource[index] = 'images/img' + index + '.png';
imgArea[index] = '#areamap_Img' + index;
$(imgArea[index]).hover(
function() {
$('#imgSwap').attr('src',imageSource[index]); // Here's where `index` is necesssary.
},
function() {
$('#imgSwap').attr('src','images/img0.png');
});
})(); // end anonymous function and execute
}; // for loop
Additionally, there's a small problem in your code you should fix just for good measure. You're not accessing your local variables correctly; you should use:
var imageSource = [];
var imageSource[0] = 'images/img0.png' //load 0 position with "empty" png
var imgArea = []
Without the "var", you're declaring and accessing global variables. (If this is your intended behavior then I apologize.)

Categories