addEventListener in JS not working as expected

2019-02-27 16:15发布

问题:

Firstly skim codes please.

index.html is :

<html><head><title>Home</title><script src="js/script.js"></script></head>
<body onLoad="init()">

<ul class="sup" id="sup">

    <li class="supitem">
        <a href="#" class="supcont">Home<div class="v"></div></a>
        <ul class="sub">
            <li class="subitem"><a href="#" class="subcont">Home1</a></li>
            <li class="subitem"><a href="#" class="subcont">Home2</a></li>
            <li class="subitem"><a href="#" class="subcont">Home3</a></li>
        </ul>
    </li>

    <li class="supitem">
        <a href="#" class="supcont">Blog<div class="v"></div></a>
        <ul class="sub">
            <li class="subitem"><a href="#" class="subcont">Blog1</a></li>
            <li class="subitem"><a href="#" class="subcont">Blog2</a></li>
            <li class="subitem"><a href="#" class="subcont">Blog3</a></li>
        </ul>
    </li>

</ul>

</body>
</html>

script.js is :

function init() {
    var sky = 0;
    var sup         = document.getElementById("sup");
    var supitems    = sup.getElementsByClassName("supitem");

    for (var i = 0, ln = supitems.length; i < ln; i++) {
        var supconts = supitems[i].getElementsByClassName("supcont");
        var subs = supitems[i].getElementsByClassName("sub");
        var supcont = supconts[0];

        supcont.innerHTML = "SuperMenu"+i;

        if (subs.length > 0) {
            var sub         = subs[0];

            supcont.addEventListener("click",function() {
                toggleVisibility(sub); });

            supcont.style.background = "#"+sky+sky+sky;
            sub.style.background = "#"+sky+sky+sky;
            sky += 4;
        }
    }
}

function toggleVisibility(object) {
    object.style.visibility =
        (object.style.visibility == "hidden" ?"visible" :"hidden");
}

What I would like to do is when I press supermenu all sub-menus' visibility to be toggled. But I don't know where I have made a mistake. When I press Supmenu0, submenus of Supmenu1 are toggled, not submenus of Supmenu1. Thanks in advance.

P.S. I think the problem is in addEventListener.

回答1:

This is a frequently asked question but I will try to give a better explanation than I have found. When you pass a function as a parameter (as when defining your event handler), javascript does not evaluate the function at that time, but instead stores the function along with a reference to its parent scope.

The function does not get evaluated until the event handler is triggered. At that time, the interpreter will check the value of sub in the parent scope. Since this will always happen after your for loop has completed, it will always find the last value of sub, i.e. whatever sub was when your for loop was completed. So all of your event listeners will use the last value of sub.

We can get the desired behavior by creating a closure. Replace this:

supcont.addEventListener("click",function() {
    toggleVisibility(sub); });

with this:

(function(localSub) {
    supcont.addEventListener("click",function() {
        toggleVisibility(localSub);
    });    
})(sub);

The reason this works is because we wrap each event handler declaration with a new parent scope by invoking an IIFE. This forces the event handler to retain a copy of the scope inside the IIFE (called creating a closure over that scope). Now, when the event handler goes looking for localSub, it will find it in the new parent scope and it will have the value we want.