This question already has an answer here:
Code gives me: A B C
When I click on A B C it always shows me the last one "vodka".
I want "martin" (for A), "lindsay"(for B), "vodka" (for C)
Please help me on my example.
myArray = [
{
letter: "A",
brand: "martin"
},
{
letter: "B",
brand: "lindsay"
},
{
letter: "C",
brand: "vodka"
}
];
var list = '';
for (var i = 0; i < myArray.length; i++) {
list += "<br>" + myArray[i].letter;
new_info = myArray[i].link;
(function(new_info) {
$(this).click(function(){ //this - refers to A or B or C
$('#box2').text(new_info);
});
}).call(this, myArray[i])
}
$('#box1').append(list);
Edit:
I said I wasn't going to write your code for you... well, I did: this fiddle does exactly what you're looking for. I solved the issue with context (
this
), closure issues and implied globals. It still needs a lot of work, but the fiddle shows what everybody has been saying:$(this)
does not, cannot and will never point to a string constant like"A", or "B"
.Sorry to say this, but your code is full of problems, but I will address the specific issue you're asking about here.
Inside the loop, you're assigning a click handler that, basically looks like this:
Where
new_info
is a variable that is declared in a higher scope. So far, so good. The problem is, the function object you're creating doesn't have its own copy of whatever value that variable (new_info
) happened to hold when that function was created. Instead, the function references that variable. So when any of those functions is invoked it$('#box2').text(new_info)
will be resolved to$('#box2').text("whatever value new_info holds when function is called")
, not$('#box2').text("whatever value new_info was holding when function was created")
. You can give each callback access to a copy by simply adding a second function to your code:What I'm doing here is creating a function, that takes an argument, and calling it immediately. I pass
new_info
as an argument, so the value ofcurrentNewInfo
will be whatnew_info
holds at that time (aka a copy)The function I called (IIFE - or Immediately Invoked Function Expression) returns the actual callback. In this callback, I don't reference
new_info
, but the argument of the IIFE:currentNewInfo
.Because each function has its own scope, that variable is enclosed (hence the name closure), and cannot be accessed or altered from outside. The only thing that can still access the
currentNewInfo
variable is the function the IIFE returned.Perhaps you are worried about name-conflicts (each callback you create uses references
currentNewInfo
), but that's not the case: each callback was created by a separate function, and therefore has access to a different scope. It's not possible to have a name conflict between scopes that don't access each other... Just to make things really simple to understand:So closures have access to a function's scope after it returns. That scope has precedence when it comes to resolving an expression to a value. To understand this better, here's a similar diagram to show you how JS resolves expressions:
Where each pink "outer environment record" is a scope of a function (closure scope of a function that has returned already or function currently being called). The last environment will either be the global object, or null (in strict mode). That's all there is to it.
Honestly, closures are tricky to get your head round at first, but once you grasp what I tried to explain here, they're great fun.
Check this link I can go on to explain the use cases and benefits and ways nested closures work, but I'd end up writing a book. The link I posted does a great job at explaining how closures work using rather silly drawings. It may seem childish, but they actually helped me a lot when I was trying to grasp the concept of lambda functions, closures and scopes out-living a function-call. The diagrams above are taken from the page I linked to, which explains the concepts a bit more in-depth, but I still think the simple, crude drawings are pretty self explanatory.
Other issues:
As someone pointed out: "What are you expecting
this
to reference". Looking at the snippet,this
will just reference the global object (window
), attaching the same/similar event handler towindow
simply doesn't make sense if you ask me.Global variables are evil, implied globals even more so. I can't see
new_info
, normyArray
being declared anywhere. The way JS resolves expressions is a tad unfortunate and falls back to creating global variables, without so much as a peep:Let's look at
foo
:Excessive DOM queries: your event-handler callbacks all look like so:
This (
$('#box2')
) is actually the same as writingdocument.getElementById('#box2')
. Which is practically English. Think about it like this: each time the client clicks on$(this)
- whatever that may be, you're accessing the DOM, and scanning it for an element with a given ID. Why not do this once and use a reference kept in memory to change the text. That saves countless DOM queries.You could use a variable, or (in light of what I explained about closures), a closure: