javascript - How to make this code work? [duplicat

2019-01-03 18:36发布

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);

1条回答
来,给爷笑一个
2楼-- · 2019-01-03 19:35

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:

function()
{
    $('#box2').text(new_info);
}

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:

$(this).click((function(currentNewInfo)
{
    return function()
    {
        $('#box2').text(currentNewInfo);
    }
}(new_info)));

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 of currentNewInfo will be what new_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:

What a closure does

Where  /\            and      /\
       ||                     ||
  is return function()      is scope of IIFE

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:

JS and scope-scanning
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 to window simply doesn't make sense if you ask me.
Global variables are evil, implied globals even more so. I can't see new_info, nor myArray 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:

var bar = 666;//global, always evil
function createGlobal()
{
    var local = 2;
    foo = bar * local;
}
createGlobal();

Let's look at foo:

JS is in createGlobal scope: var local is declared, and assigned 2.
   foo is used, and assigned bar*local
    ||                       ||   \\=>found in current scope, resolves to 2
    ||                       ||
    ||                       \\=>found in global scope, resolves to 666
    ||
    ||
    ||=> JS looks for foo declaration in function scope first, not found
    ||
    ||=> moves up 1 scope (either higher function, or global scope)
    ||
    \\=>Global scope, foo not found, create foo globally! - hence, implied global
             \\
              \\=>foo can now be resolved to global variable, value undefined

Excessive DOM queries: your event-handler callbacks all look like so:

$('#box2').text(new_info);

This ($('#box2')) is actually the same as writing document.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:

var list = (function(box2, list, i)
{//list & i are arguments, so local to scope, too
    for (i = 0; i < myArray.length; i++)
    {
        list += "<br>" + myArray[i].letter;//<-- don't know why you use this
        //new_info = myArray[i].link; no need for this var
        $(this).click((function(new_info)
        {//new_info is closure var now
            return function ()
            {//box2 references DOM element, is kept in memory to reduce DOM querying
                box2.text(link);
            };
        }(myArray[i].link));//instead of new_info, just pass value here
    }
    return list;//return string, assign to outer variable
}($('#box2'), ''));//query dom here, pass reference as argument
查看更多
登录 后发表回答