孩子们如何听取/捕捉父母的事件?

13

父元素如何触发自定义事件以通知其子元素/同级元素?例如:

<div id="div1">
   <div id="div2"></div>
</div>
添加了 addEventListener('customEvent2', doSth) 事件,然后 触发了一个自定义事件 (customEvnet2),但是这样永远无法触发 中的 doSth 函数。

示例代码: http://jsfiddle.net/r4tcT/2/

“div 1 触发 customEvent 2” 按钮从未起作用。

因此,当父元素触发自定义事件(dispatchEvent[IE9]/fireEvent[IE9-]/trigger[jQuery])时,子元素无法捕获该事件。

有什么解决办法吗?


3
我相信这是不可能的,除非你进行某种黑客手段,因为事件会像冒泡一样向上传递到DOM树,而不会下传到子元素。 - haynar
4个回答

7
你所说的区别可能是“捕获”事件模型或“冒泡”事件模型之间的差异。jQuery的trigger操作基于冒泡模型,可能是因为这是更受支持的事件模型,主要归功于Internet Explorer。冒泡模型仅向上遍历元素的父级...这就是当从div1触发时,你的事件不会在div2上触发的原因,因为它始终向上冒泡而不是向下。我以前没有尝试过使用原生函数进行自定义事件,但是大多数现代浏览器在设置事件监听器时允许您决定使用哪种类型的模型。
addEventListener (type, listener[, useCapture])

https://developer.mozilla.org/zh-CN/docs/DOM/element.addEventListener

如果将最后一个参数设为 true,则事件侦听器应在捕获阶段触发(即事件向下遍历DOM树时)。如果设置为 false,则事件将在冒泡阶段触发,该阶段在向上遍历DOM树时发生。

这里讨论了这个问题:

事件捕获与事件冒泡

正如我所说,我不确定这对于专门定制的事件是否有效。我非常确定您不能使用jQuery(至今)来实现此目的,可能是因为旧浏览器缺乏支持。

更正

显然,我之前猜测的方法行不通。我认为这是由于“捕获”一词让你想到捕获用户输入 - 当涉及定制事件时,没有办法定义新的用户输入类型。因此,我编写了这个快速的jQuery插件... 它只经过了大致测试,但逻辑应该是正确的 - 希望它有用:

/**
 * unbubble v0.2
 *
 * trigger an event down through the children of a collection, 
 * rather than up through it's parents
 *
 *  @update 2013/03/18 - fixed the problem of triggering bubble phase each
 *    step down the element tree as pointed out by @vine.
 */
$.fn.unbubble = function( eventNames ){
  var names = eventNames.split(' '), 
      non = names.length, 
      args = Array.prototype.slice.call(arguments);
  /// our own trigger function designed to bubble down... not up!
  var trigger = function(){
    var i, events, elm = $(this);
    /// make sure we can read the events array
    if ( $._data ) {
      /// make sure events is defined
      if ( (events = $._data(this, 'events')) ) {
        /// do a quick check, saves firing trigger on every element found
        for ( i=0; i<non; i++ ) {
          /// make sure our eventName appears in the event list
          if ( names[i] && ( names[i] in events ) ) {
            /// trigger the standard jQuery trigger function
            elm.triggerHandler.apply(elm, args);
            /// escape as trigger should fire for multiple names
            break;
          }
        }
      }
    }
    /// if we can't access the events array, just trigger and hope
    else {
      /// trigger the standard jQuery trigger function
      elm.triggerHandler.apply(elm, args);
    }
    /// trigger for all the children, and on, and on...
    elm.children().each(trigger);
  };
  /// foreach element trigger now...
  this.each(trigger);
}

/**
 * Example usage
 */
$(function(){
  /// bind our event as usual
  $('.div2').bind('customEvent', function(){
    alert('I should trigger!');
  });
  /// rather than use trigger, fire with unbubble
  $('#div1').unbubble( 'customEvent' );
});

感谢回复,但根据W3C的规定,使用addEventListener并将第三个参数设置为true将会对事件进行捕获阶段处理。然而,我已经更新了代码,不再使用jQuery(这里是新代码http://jsfiddle.net/EDjqu/4/),但似乎捕获阶段仍然无法正常工作。 - CLB
是的,我刚刚意识到这一点...认为我应该测试一下,因为我不知道它是否会成功...这让我开始编写一个快速的jQuery插件。 - Pebbl
1
谢谢,我想除了循环遍历子元素之外,没有其他选择,就像@haynar在帖子中评论的那样。 - CLB
完全不用担心.. :) 但是有一件事我想说,就是 haynar 的解决方案硬编码了特定标识符,需要在 div1 上设置 customEvent2 - 这不是很理想 (正如他所承认的) - 我不建议你这样做 *(除非你只是临时实现这些事件监听器)*。上面的插件被泛化为与 $().trigger 完全相同的方式工作,支持任何事件、传递额外参数,并且可以处理任何父子关系之间的距离... 这将使您的代码更整洁、更易于管理,并且更符合 jQuery 的精神。 - Pebbl

1
pebbl的回答很好,但它有缺陷。捕获阶段在某种程度上是通过从文档到相关元素的事件正常触发来模拟的。但问题在于,调用任何元素上的标准jQuery触发器函数将立即跟随一个从该元素开始并向上冒泡的阶段。因此,我认为他可以坚持直接从元素集合中访问事件数据并直接调用它,而不使用标准触发器函数,就像这样:
var JQ_LT_17 = parseFloat($.fn.jquery) < 1.7;

function getEventsData(element) {
        return JQ_LT_17 ? $(element).data('events') : $._data(element).events;
}

代码片段借用自jQuery.bind-first库v0.1 Vladimir Zhuravlev


+1 很好的观点——完全没想到——我需要在有时间的时候进行修改 :) - Pebbl

0

customEvent2 只绑定到 div2。当你尝试在 div1 上触发它时,什么也不会发生,因为该事件对于 div1 不存在。

如果你想触发 customEvent2,它必须在实际绑定的元素(或其子元素)上触发。


1
这正是我不想要的,例如我有div2_a、div2_b、div2_c等等,可能还有很多,那么我就必须遍历所有的div2才能触发事件。 - CLB

0

我已经稍微尝试了一下这段代码,现在在这里是我的成果。虽然有点取巧,但或许可以帮助你解决问题?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接