Maintain reference inside a for-loop Javascript - javascript

I have the following code:
for(var i = 0; i < nodelist.length; i++) {
var x = functionThatCreatesADivElement();
someElement.appendChild(x.getDiv()); // this works fine
nodelist[i].onclick = function() {
x.someFunction(); // this always refer to the last 'x' object
}
}
function functionThatCreatesADivElement() {
var div = document.createElement("div");
this.someFunction = function() {}
this.getDiv = function() {
return div;
}
return this;
}
the problem is that the execution of nodelist[0].onclick is exactly the same as nodelist[4].onclick (assuming that i = 4 is the last node).
I believe the references of the previously iterated are changing to point to the currently iterated element.
What is the proper way of doing this?
EDIT: Added some more code and changed the name of the function cause it was too confusing

You have two problems. The first problem is that JavaScript variables don't have block scopes.
From MDN:
When you declare a variable outside of any function, it is called a global variable, because it is available to any other code in the current document. When you declare a variable
within a function, it is called a local variable, because it is available only within that
function.
JavaScript does not have block statement scope;
You aren't enclosing a the x variable in a function, so all of your onclick callbacks are using the same x variable, which point to whatever element is last in the loop since that will be the last one to have overwritten x.
Doing this for your loop should work:
nodelist.forEach(function (nodeitem) {
var x = functionThatCreatesADivElement();
someElement.appendChild(x.getDiv());
nodeitem.onclick = function() {
x.someFunction();
}
});
The second problem is that your functionThatCreatesADivElement() constructor function is not being called correctly. Use new functionThatCreatesADivElement() since you are invoking a constructor function.

Solved. I had to use
var x = new functionThatCreatesADivElement();
function functionThatCreatesADivElement() {
var div = document.createElement("div");
this.someFunction = function() {}
this.getDiv = function() {
return div;
}
//return this; //Using new instead of returning this
}

Related

An function argument that contains an array

So, I have a question. Inside a function I have a createElement() function that creates a li. I also use the setAttribute() function to add a onclick(). But here's my question, can i store like an array inside the onclick(), so i can get the array in a new functions argument? What should i put inside 'changeClass("' + '");') ? ("names" is a global variable that contains an array. "numberOfCookies" is nothing to worry about.)
function getClassCookie() {
var cookiesArray = document.cookie.split(';');
numberOfCookies = cookiesArray.length;
for (var i = 0; i < cookiesArray.length; i++) {
var nameValueArray = cookiesArray[i].split('=');
var split = nameValueArray[1].split(',');
var className = split[0];
var copySplit = split.slice();
copySplit.splice(0, 1);
names = copySplit;
var newLi = document.createElement('li');
var textNode = document.createTextNode(className);
newLi.setAttribute('onclick', 'changeClass("' + '");');
newLi.setAttribute('onclick', 'hideDropdown();');
newLi.appendChild(textNode);
var parent = document.getElementById('classes');
parent.appendChild(newLi);
}
}
function changeClass(c) {
names = c;
}
You can use the array in the handler with a closure, if you attach the handler properly with Javascript, such as with addEventListener. (Inline handlers are generally difficult to manage and aren't considered good practice, anyway). For example, once you've declared newLi, simply reference the array you want to call changeClass with later:
newLi.addEventListener('click', function() {
changeClass(copySplit);
});
The above code will allow the copySplit that was created in that iteration of the for loop to be the one that's referenced when the newLi is clicked later. (This technique isn't possible when you use inline handlers)
One more thing - variables declared with var are hoisted and have unintuitive function scope, rather than block scope. Make sure to use const or let instead, which have block scope (that way each iteration of the loop has separate bindings for variables, rather than all iterations sharing a single set of variables):
for (let i = 0; i < cookiesArray.length; i++) {
const nameValueArray = cookiesArray[i].split('=');
const split = nameValueArray[1].split(',');
const className = split[0];
const copySplit = split.slice();
// etc
Using string concatenation for the function name while attaching it to an attribute isn't really a good approach. Just use the function hook. If you need to pass parameters into it, then you can do that as well in an anonymous call.
In the example below, note that the demo array maintains its state on subsequent clicks because of its scope.
function handler(arr){
arr.push(arr.length);
console.log(arr);
}
(function demo(){
var newButton = document.createElement('button');
newButton.textContent = "Demo";
var someArray = [];
newButton.onclick = function(){
handler(someArray);
}
document.body.appendChild(newButton);
})()

Understanding JavaScript Closures with a small example

I am trying to get around understanding javascript closures from a practical scenario.I know from a theoretical perspective , With the help of closures inner functions can have access to the variables in the enclosing function i.e parent function.
I have read a couple of questions on stackOverflow as well.
i am really missing the point of what is happening here?
var foo = [];
for(var i=0;i<10;i++){
foo[i] = function(){
return i;
}
}
console.log(foo[0]());
This gives me out a 10. Most of the articles say that by the time it reaches the inner anonymous function, The for loop is getting executed as a result the last value that is present in the loop which is 10 is being printed.
But i am still not able to get to the bottom of this.
On Contrary, If i use something like:
var foo = [];
for(var i=0;i<10;i++){
(function(){
var y =i;
foo[i] = function(){
return y;
}
})();
}
console.log(foo[0]());
I am getting the output.Any help would be highly appreciated.
maybe this code block helps
var foo = [];
for(var i = 0; i < 10; i++) {
foo[i] = function() {
return i; // is a reference and will always be the value, which 'i' have on function execution
}
}
// 'i' is 10 here!
console.log(foo[0]()); // executing the function will return the current value of 'i'
///////////////////////////////////////
var foo = [];
for(var i=0;i<10;i++) {
/* thats a IIFE (immediately invoked function expression) */
(function(a) { // 'a' is now a local variable
foo[a] = function() { // defines a function
return a; // is a reference to local variable 'a'
};
})(i); // <- passing the current value of i as parameter to the invoked function
}
// 'i' is 10 here
console.log(foo[0]()); // returns the reference to 'a' within the same scope, where the function was defined
In your first scenario, all of your functions added to the foo array are referencing the same var i. All functions will return whatever i was set to last, which is 10 because during the last iteration of the loop that's what it's value was set to.
In the second scenario, you are Immediately Invoking this function:
(function(){
var y =i;
foo[i] = function(){
return y;
}
})();
By immediately invoking it you are effectively locking in the local state of var y, for each iteration of the loop - it provides a unique scope for each function added to the array.

JS function to return function, shares data which should be private

I'm trying to create a function which returns another function. I want separate information when each of the inner function is run, but this isn't happening. I know that explanation is not great, so I've put together a small example.
var testFn = function(testVal) {
return (function(testVal) {
var test = testVal;
this.getVal = function() {
return test;
}
return that;
})(testVal);
}
var a = testFn(4);
var b = testFn(2);
console.log(b.getVal(), a.getVal());
This outputs 2, 2. What I would like is 2, 4 to be output. I know this isn't explained perfectly, so if it's not clear what I'm trying to achieve, can someone explain why the variable seems to be shared across the two functions?
Thanks
Like this ?
var testFn = function(testVal) {
var test = testVal
return {
getVal: function() {
return test
}
}
};
var ab = testFn (4)
var ac = testFn (2)
console.log(ab.getVal(),ac.getVal()) //4 //2
The problem in your code is this.getVal() / returning this
because 'this' refers to the global scope / Window
You are glubbering with the global namespace and overwriting Window.getVal() , the moment you are setting b = testFn (2)
This results in overwriting as method getVal too because they both refer to the global Object and always share the same method getVal
Therefore they share the same closure and are outputing 2
console.log("The same: " + (Window.a === Window.b)) // true
console.log("The same: " + (a === b)) // true
you can see that if you change it a little:
var testFn = function(testVal) {
var x = {}
return (function(testVal) {
var test = testVal;
x.getVal = function () {
return test;
}
return x
})(testVal);
}
var a = testFn(4);
var b = testFn(2);
console.log(b.getVal(), a.getVal());//4 2
it suddenly works because it results in 2 different Objects returned (btw you don't even need the outer closure)
console.log("The same: " + (a === b)) // false
Here are the JSbins First / Second
I hope you understand this, I'm not good in explaining things
If theres anything left unclear, post a comment and I'll try to update the answer
This question comes down to the context in which functions are invoked in JavaScript.
A function that is invoked within another function is executed in the context of the global scope.
In your example, where you have this code:
var testFn = function(testVal) {
return (function(testVal) {
var test = testVal;
this.getVal = function() {
return test;
}
return this;
})(testVal);
}
The inner function is being called on the global scope, so this refers to the global object. In JavaScript a function executed within another function is done so with its scope set to the global scope, not the scope of the function it exists within. This tends to trip developers up a fair bit (or at least, it does me!).
For argument's sake, lets presume this is in a browser, so hence this refers to the window object. This is why you get 2 logged twice, because the second time this runs, this.getVal overwrites the getVal method that was defined when you ran var a = testFn(4);.
JavaScript scopes at function level, so every function has its own scope:
var x = 3;
function foo() {
var x = 2;
console.log(x);
};
console.log(x); //gives us 3
foo(); // logs 2
So what you want to do is run that inner function in the context of the testFn function, not in the global scope. You can run a function with a specific context using the call method. I also recorded a screencast on call and apply which discusses this in greater detail. The basic usage of call is:
function foo() {...}.call(this);
That executes foo in the context of this. So, the first step is to make sure your inner function is called in the right context, the context of the testFn method.
var testFn = function(testVal) {
return (function(testVal) {
var test = testVal;
this.getVal = function() {
return test;
}
return this;
}.call(this, testVal);
}
The first parameter to call is the context, and any arguments following that are passed to the function as parameters. So now the inner function is being called in the right scope, it wont add getVal to the global scope, which is a step in the right direction :)
Next though you also need to make sure that every time you call testFn, you do so in a new scope, so you're not overwriting this.getVal when you call testFn for the second time. You can do this using the new keyword. This SO post on the new keyword is well worth reading. When you do var foo = new testFn() you create and execute a new instance of testFN, hereby creating a new scope. This SO question is also relevant.
All you now need to do is change your declaration of a and b to:
var a = new testFn(4);
var b = new testFn(2);
And now console.log(b.getVal(), a.getVal()); will give 2, 4 as desired.
I put a working example on JSBin which should help clear things up. Note how this example defines this.x globally and within the function, and see which ones get logged. Have a play with this and hopefully it might be of use.
The output you get is (2,2) because when you do
var that = this;
what you actually get is the global object (window),
the object that holds all the global methods and variables in your javascript code.
(Note that every variable that is not nested under an object or function is global and
every function that is not nested under an object is global, meaning that functions that are nested under a function are still global)
so, when you set:
var test = testVal;
this.getVal = function() {
return test;
}
you actually set the function "getVal" in the global object, and in the next run you will again set the same function - overriding the first.
To achieve the affect you wanted I would suggest creating and object and returning it in the inner function (as #Glutamat suggested before me):
var testFn = function(testVal) {
return new Object({
getVal: function() {
return testVal;
}
});
}
var a = testFn(4);
var b = testFn(2);
console.log(b.getVal(), a.getVal());
In this way, in the outer function we create an object with an inner function called "getVal" that returns the variable passed to the outer function (testVal).
Here's a JSBin if you want to play around with it
(thanks to #Glutamat for introducing this site, I never heard of it and it's really cool :D)

javascript passing variable to a function inside a function

I have a problem getting the value of 'name' displayed with the following:
for (var name in array) {
var hoverIn = function() {
alert(name);
};
var hoverOut = function() {
};
thing.hover(hoverIn, hoverOut);
}
What I get is an alert window with the last value of name. Clearly I am doing something wrong and I suspect it's a simple fix. Can anyone help?
Thanks.
It's closure problem, name, after that iteration is the last name in array, and callback for hovers isn't executed right away when the iteration happens, thus when the hover function is actually executed, name will always be the last in array.
You need to use IEFE (Immediately executed function expression):
for (var name in array) {
// pass in name to the anonymous function, and immediately
// executes it to retain name when the particular iteration happens
var hoverIn = (function(name) {
return function() {
alert(name);
}
})(name); // if you notice, the pattern is (function(name) {})(name)
// the first () creates an anonymous function, the (name)
// executes it, effectively passing name to the anon fn
var hoverOut = (function(name) {
// same pattern if you need to re-use name inside here, otherwise just
// function() { } should suffice
})(name);
thing.hover(hoverIn, hoverOut);
}
To avoid duplicates of (function() { })() (which honestly is getting tiring to look at), you could also, as #pimvdb pointed out, wrap the whole body in a closure:
for (var name in array) {
(function(name) {
var hoverIn = function() {
alert(name);
}
var hoverOut = function() {
}
thing.hover(hoverIn, hoverOut);
})(name); // closure of for loop body
}
Add a variable inside the loop
var thisName = name;
and then use that in your function
alert(thisName);
You have two ways of dealing with this problem.
The first thing to know is that scope only happens at function level, not within loops in javascript.
If you set a variable within a function from an outside source and don't execute it right away,the variable will be changed over the course of your loop.
You can solve this by closing other the variable:
var names = ["john","paul","george","ringo"];
var store = {};
//this function receives the data as a parameter
//so it will be a safe copy.
function createFunc(name){
//just return a function that will alert the name.
return function(){
alert(name);
}
}
for (var i in names) {
var hoverIn = createFunc(names[i]);
store[names[i]]=hoverIn;
}
store["john"]();
The other way is to create an anonymous function that executes right away
within the loop:
var names = ["john","paul","george","ringo"];
var store = {};
for (var i in names) {
//the function receives the i as a parameter
//and executes, so n is a safe copy of i
(function(n){
var hoverIn = function(){
alert(names[n]);
}
store[names[n]]=hoverIn;
})(i);
}
store["john"]();
Everything is a problem related to closure.
Look at wikipedia for more info.
You should create closure:
for (var name in array) {
var hoverIn = (function() {
return function() {
alert(name);
};
}());
var hoverOut = function() {
};
thing.hover(hoverIn, hoverOut);
}

Binding "this" when passing an object's member function

I had a "class" defined and was making only one instance of it. The instance possessed a member function that would end up being passed around (it's a mouse handler, but that's not important). Since I would only ever make one instance of my "class", I decided to rewrite it as a singleton by using an object literal.
So I have
var mySingleton = {
theObjects : [];
}
mySingleton.mouseHandler = (function() {
var that = this;
return function (e) {
for (var indx = 0; indx < that.theObjects.length; indx++) {
// do something to that.theObjects[indx];
}
}
}());
mySingleton.addObject = function(newObj) {
this.theObjects.push(newObj);
}
However, when I try to use this code (after adding a few objects), I keep getting an that.theObjects is undefined error. It's referring to the line in the for loop.
Update for 2015 – Use Function.bind() to specify the value of this within the function. Then, instead of using that, you can use this.
mySingleton.mouseHandler = function (e) {
for (var indx = 0; indx < this.theObjects.length; indx++) {
// do something to this.theObjects[indx];
}
}.bind(mySingleton);
This doesn't work if you want mouseHandler to have the context of the 'moused' element. For that, use my original answer below.
If you need to support IE8 or (heaven forbid) earlier, you'll need to use a polyfill.
Since you are calling the function that creates mouseHandler immediately, it is run in the context of window, not mySingleton. So that refers to window. Instead of calling it immediately, just change it to a method so that it runs in the context of mySingleton:
mySingleton.getMouseHandler = function() {
var that = this;
return function() { ... };
};
myElement.onclick = mySingleton.getMouseHandler();
Of course, since you are already using a singleton, you can just reference it directly. In your click handler, instead of checking that.theObjects, check mySingleton.theObjects. Or, in mouseHandler change var that = this to var that = mySingleton.
Edit: Or, pass the context to your anonymous function when you call it:
mySingleton.mouseHandler = (function() {
var that = this;
return function (e) {
for (var indx = 0; indx < that.theObjects.length; indx++) {
// do something to that.theObjects[indx];
}
}
}).call(mySingleton);
There are a few popular ways to do this. First, super-simple solution is just reference mySingleton directly and bypass the confusion associated with this. Instead of that.theObjects just do mySingleton.theObjects and move on with your life and things will work fine.
However, there is a common pattern to do this binding. Here's how underscore.js does it
Check out the annoted source to underscore, where you will find this
_.bind = function(func, obj) {
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
var args = slice.call(arguments, 2);
return function() {
return func.apply(obj, args.concat(slice.call(arguments)));
};
};
The other answers here so far are also correct. Providing my viewpoint here in case it helps.
The key to understanding why the code doesn't behave as you expect requires understanding how this works in JavaScript. The problem is that this depends on how the function is called.
First, if you call the function in the method style, this is what you'd expect:
mySingleton.mouseHandler(); // this === mySingleton
If you attach the function to something esle, that works too.
var anotherSingleton = {};
anotherSingleton.foo = mySingleton.mouseHandler;
anotherSingleton.foo(); // this === anotherSingleton
If you detach the function, this becomes the global scope object (window)
var foo = mySingleton.mouseHandler;
foo(); // this === window
And finally, you can force this to be something else using call or apply:
var randomThingy = {};
mySingleton.mouseHandler.call(randomThingy); // this === randomThingy
The takeaway is that this is determined at runtime based on the context of how the function was called. Often, frameworks that allow you to make "classes" abstract these details from you by implicitly applying the bind pattern on your behalf. This is why it used to work, and no longer does.
As others have mentioned, you can change your handler to reference the variable by its scoped name (mySingleton) or otherwise bind it as discussed.
Here's an article I wrote on the subject a few years ago that goes into more detail: http://trephine.org/t/index.php?title=Understanding_JavaScript%27s_this_keyword
Hope this helps!

Categories

Resources