防止循环 JavaScript 事件

Posted

技术标签:

【中文标题】防止循环 JavaScript 事件【英文标题】:Preventing cyclic JavaScript events 【发布时间】:2015-01-20 17:37:42 【问题描述】:

这个问题恰好涉及 D3 特定的功能,但不是 D3 特定的问题。我有两个 D3 图表,我创建并附加了缩放功能,目的是保持缩放比例和变换位置相等,这样当一个图表缩放时,另一个图表会自动缩放到相同的比例和位置。为此,我在放大一个图表时触发一个自定义事件,以便我可以自动调整另一个图表:

// Within the chart definition:
var svg = ...; // The chart body definition
var zoom =  d3.behavior.zoom()
    .on("zoom", function(e)
        // do stuff...
        $("#chart-div").trigger(
            type: "my.zoom",
            translate: d3.event.translate,
            scale: d3.event.scale
        );
    );
svg.call(zoom);

然后我创建允许我手动调整每个图表的缩放级别的方法,在此过程中触发 D3 zoom 事件:

var adjustZoom = function(t,s)
    svg.transition().duration(500).call(zoom.scale(s).translate(t).event);
;

然后我将事件侦听器附加到我的每个图表,这样当我与其中一个交互并导致其缩放级别发生变化时,另一个会自动更新:

$("#chart-div1").on("my.zoom", function(e)
    chart2.adjustZoom(e.translate, e.scale);
);
$("#chart-div2").on("my.zoom", function(e)
    chart1.adjustZoom(e.translate, e.scale);
);

明显的问题是调用adjustZoom 方法会触发D3 zoom 事件,因此在两个图表上附加事件侦听器会创建周期性事件,因为每个图表都试图无限调整另一个图表。

1. chart1 [manual zoom]      --> trigger d3.zoom --> triggers my.zoom
2. chart2 [my.zoom listener] --> trigger d3.zoom --> triggers my.zoom
3. chart1 [my.zoom listener] --> trigger d3.zoom --> triggers my.zoom
etc...

我想实现的是一种在决定触发另一个my.zoom事件时检查my.zoom事件是否已经被触发的方法,以防止事件的循环触发(防止上面示例中的步骤3) ,但我不知道该怎么做。无论如何,函数或事件是否可以知道触发它的事件然后自行停止?

【问题讨论】:

【参考方案1】:

想到的第一个选项,如果translatescale 不是模糊值,则使用当前的translatescale 调用adjustZoom 是无操作的。因此,如果chart1 的值发生变化,它会引发事件,更改chart2 的值,然后chart2 会引发事件,但chart1 会打破循环,因为它的translatescale 已经是这些值。

另一种选择是在事件中传递更改的源元素,然后如果是启动它的图表,则不在图表上触发它。

例如,触发时:

$("#chart-div").trigger(
    type: "my.zoom",
    translate: d3.event.translate,
    scale: d3.event.scale,
    source: this              // <=== The new bit
);

然后在处理时:

if (chart2[0] !== e.source) 
    chart2.adjustZoom(e.translate, e.scale);

请注意,该代码示例假定 chart1chart2 是围绕单个元素的 jQuery 包装器;如果不是,请相应调整。

【讨论】:

有趣的想法,但我不认为这会解决我的问题。这些事件仍然会乒乓球,因为每个图表都会触发一个新的my.zoom,并将其作为源。我可能必须在每个事件处理程序中传递初始 source 值,以确保图表在对其最初触发的事件链做出反应时不会触发事件。这可能有效,但不会很优雅。 @willOEM:是的,你需要回显源代码——但我没有看到任何不雅之处。【参考方案2】:

我找到了针对我的问题的 D3 特定解决方案:

D3 事件对象d3.event 包含一个嵌套对象sourceEvent,它表示触发 D3 事件的先前事件的堆栈。这个嵌套对象中的底部事件是一个简单的 DOM 事件。例如,对于缩放,这可能是鼠标滚轮或单击事件。在我的示例中,自定义事件表示为具有所有常用事件键和属性的普通对象,因此我更改了缩放事件事件处理程序,如下所示:

var zoom =  d3.behavior.zoom()
.on("zoom", function(e)
    // do stuff...
    if (typeof d3.event.sourceEvent.sourceEvent != 'object')
        $("#chart-div").trigger(
            type: "my.zoom",
            translate: d3.event.translate,
            scale: d3.event.scale
        );
    
);

这个新测试的作用是检查sourceEvent 是否有父事件,如果有,确保它不是普通对象(我的自定义事件)。现在,当我缩放一个图表时,它会触发一个my.zoom 事件,该事件会触发第二个图表的缩放功能,但第二个图表不会触发它自己的my.zoom 事件。

很高兴知道是否有类似的通用方法来处理 javascript 事件,但对于我的用例,这个解决方案可能会做。

【讨论】:

以上是关于防止循环 JavaScript 事件的主要内容,如果未能解决你的问题,请参考以下文章

js Event Loop 事件循环

JavaScript&jQuery.DOM事件

Tkinter:如何使用线程来防止主事件循环“冻结”

如何防止javascript中的事件冒泡

等到 for 循环内的所有函数调用结束其执行 - Javascript

Event 事件