当为循环中的每个元素分配不同的事件监听器时,它们都 运行 相同的功能,尽管使用了 .bind() 和闭包

When assigning different eventListeners to each element in a loop they all run the same function, DESPITE use of .bind() and closures

问题

该项目使用 CreateJS,构建后您可以通过添加 JSON 数据在 Canvas 中创建各种游戏和练习。为了演示这个问题,我将使用 'Alphabet Board' 的示例,您可以在其中单击每个字母的图片以听到大声朗读。问题是它们都播放最后一个字母的声音。

创建练习时,JSON-数据从数据库加载。它添加了用户指定的所有内容,但是在添加区域时出现问题。这些是用户定义的坐标、大小以及单击时要执行的操作。它通过一个循环来实现这一点,检查要添加的区域类型(圆形、矩形、隐藏矩形等),通过 CreateJS 添加它,然后分配一个 eventListener 来执行预定义的操作。然而,最后,所有的事件监听器执行相同的动作,即最后一个。但是,我正在使用 .bind() 并且还尝试在具有相同结果的匿名函数中创建闭包。

例子

一个区域的数据是这样的(以字母A为例):

{"coords":"25,30,67,55","type":"rectangleHidden","actions":[{"do":"playSound","data":{"asset":{"id":"a","src":"a.mp3"}}}]}

您可以在这里使用 JSON 在线编辑器更好地查看: Data for A to C

所有资产都已预加载并可访问。加载所有资产后,构建场景并运行此函数:

p.addAreas = function (areas, event) {
    var i = 0,
    length = (typeof areas != 'undefined') ? areas.length : 0;

    for (; i < length; i++) {
        var x = i;
switch (areas[i].type) {                
            case 'rectangleHidden':
                var rectangleHidden = new ui.Rectangle(areas[i].coords, this.STRIKE, this.FILL, this.STROKE_STYLE, false);
                rectangleHidden.alpha = 0.01; 
                this.addChild(rectangleHidden);
                rectangleHidden.addEventListener('click', this.doActions.bind(event, areas[i].actions, this));
                break;
            default :
                break;
        }
    }
}

我省略了开关的不同情况,因为这个例子只使用了 rectangleHidden。下一个也是如此,我只包含了 playSound。所以addEventListener分配的函数是这个:

p.doActions = function (actions, scene) {
    "use strict";
    var i = 0,
    length = actions.length;


    // cleans up scene
    p.cleanUp(scene);

    // adds actions in sequence order
    for (; i < length; i++) {
        var action = actions[i],
        data = action.data;

        // switch over action
        switch (action.do) {
            case 'playSound':
                var delayTime = Number(data.delay || 0);
                var looper = Number(data.loop || 0);
                new createjs.Sound.play(data.asset.id, { delay: delayTime, loop: looper });
                break;
            default:
                break;
        }
    }
}

即使使用了.bind(),在这种情况下,所有的字母都会播放声音"c.mp3" 而不是他们各自的声音。我哪里错了? eventListener 是否在某处被覆盖?

我试过的

我尝试修改添加 eventListener 的代码,如下所示:

rectangleHidden.on('click', function (scene, action) {
                    return scene.doActions.bind(event, action, scene);
                }(this, areas[i].actions));

但它仍然产生相同的结果。

我还尝试在另一个项目中创建一个经典闭包,其中事件监听器使用循环中的 i 值调用此函数:

function saveNumber(value) {
    return function () { console.log(value); };
}

在另一个项目中,正如预期的那样,每个列表项在单击时都会从控制台的数组中写入它们的 自己的 编号。当我尝试将此函数添加到当前项目中的事件监听器时,它仍然每次都写出循环的最后一个值,在本例中为 2rectangleHidden.addEventListener('click', saveNumber(i));

它们之间有什么区别?(该函数是在循环外和 addAreas 表达式外定义的。)

附加信息

我可以补充一点 this 指的是 Canvas-stage 本身,我最初没有写这段代码,我只是想解决这个问题。在我从 createjs-2013.12.12 更新到 createjs-2015.05.21 之前,相同的代码似乎一直在工作,但切换回它确实没有任何区别。有需要的可以私信我哦!

我已阅读 JavaScript closure inside loops – simple practical example,虽然乍一看它似乎可以帮助我,但它并没有完全解决我的问题。在答案中解释了如何使用闭包和 .bind() 来解决问题,但是,我的问题是这种情况正在发生尽管使用那些。

问题确实与 CreateJS 和更新到最新版本有关。以前 类 是通过使用初始化方法扩展的,后来改为使用 SuperClass_constructor,如 here.

所述

由于旧代码,未正确创建按钮并导致 .on() 和 .addEventListener() 方法出现问题。循环一直有效!