如何在JavaScript(或jQuery)中实现自定义广播事件?

12

我希望实现一个自定义事件,可以“广播”,而不是发送到特定的目标。只有那些已经将自己注册为此类事件侦听器的元素将收到它们。

我考虑的方案如下。

首先,在代码的各个位置,会有以下形式的语句

some_subscriber.on_signal( 'some_signal', some_handler );

我使用术语signal作为“广播事件”的简称。在上面的表达式中,some_subscriber通过提供一个处理程序将自己注册为这些信号中的一种类型(称为“some_signal”)的监听器。

在代码的其他地方将会有形如以下语句的代码:

publisher.signal_types[ 'some_signal' ].broadcast( event_data );

当执行这样的语句时,会生成一个新的事件并进行“广播”。我指的是调用方法的代码没有直接关于其所发出信号的侦听器的信息。


我在这个 jsFiddle中实现了这个想法的草图,主要是为了说明我上面描述的内容1。(它肯定不是生产级别的,而且我并不特别有信心能够实现。)

此实现的关键元素如下所示。首先,发布者对象不会跟踪其订阅者,可以在下面所示的创建此类发布者的工厂方法的实现中看到:

function make_publisher ( signal_types ) {

    // ...

    var _
    ,   signal = {}
    ,   ping = function ( type ) {
                   signal[ type ].broadcast( ... );
               }
    ;

    signal_types.forEach( function ( type ) {
                             signal[ type ] = $.register_signal_type( type );
                         } );

    return { signal_types: signal_types, ping: ping };
}

这个发布者对象仅暴露了两个项:它广播的信号类型(在signal_types中)和一个名为ping的方法。当调用它的ping方法时,发布者会响应通过广播一个信号:

signal[ type ].broadcast( ... )

这个广播的最终接收者在这段代码中没有出现

其次,在代码的其他位置,订阅方会注册自己作为这些广播信号的侦听器,像这样:

    $( some_selector ).on_signal( signal_type, some_handler );
注意:基本上不可能用一个既小又现实的例子来说明此方案的原理。这是因为该方案的优势在于它支持发布者代码和订阅者代码之间非常松散的耦合,而这在一个小的示例中从未必要。相反,在一个小的示例中,实现这样的松散耦合的代码无论如何都会显得不必要地复杂。因此,重要的是要记住,这种明显的过多复杂性是环境的产物。松散的耦合在更大的项目中非常有用。特别是通过发布者/订阅者类型模式的松散耦合是MVC的基本特征之一。

我的问题是:有没有更好(或至少更标准)的方法来实现“广播”自定义事件的效果?

(我对基于jQuery和“纯JS”的答案都很感兴趣。)


1本文的早期版本备受普遍的不理解和(当然)典型的投票否定。除了一个例外,所有的评论都质疑了本文的前提,并直接质疑了我的事件驱动编程的基础等等。我希望通过展示一个工作示例来说明我所说的至少不会像我仅用文字描述时那样难以想象。幸运的是,在早期帖子中我得到的唯一有帮助的评论告诉我函数jQuery.Callbacks。这确实是一个有用的提示;本文提到的简要实现是基于jQuery.Callbacks


1
一个通用的自定义事件库......https://github.com/mmikowski/jquery.event.gevent。 - Piyuesh
1
我很好奇你是否知道本地的**dispatchEventaddEventListener方法,特别是当两者都可以在document对象上使用时,因此,唯一的依赖关系是全局document对象,用于分别进行广播监听**。我也很想知道,如果你知道它们,你注意到了什么缺点,促使你提出自己的解决方案。谢谢。 - Tahir Ahmed
1
既然您已经了解了“jQuery.Callbacks”,对于这个问题,还有什么是缺失的呢?它是一种发布/订阅形式。为什么它不能胜任呢? - jfriend00
1
如果我理解正确的话,你想要解耦“发布者”和“订阅者”,并且document.dispatchEvent()document.addEventListener应该可以提供这种功能。这样,你的document对象就成为了所有事件的通道,一个代理。而且,你也可以使用方法来跨浏览器地使用它们。 - Tahir Ahmed
1
jQuery.Callbacks的示例应该可以完成您想要的一切,除了“ping”之外。添加ping方法可能会使代码失去其简洁性,但这取决于.ping()的具体目的。 - Roamer-1888
显示剩余3条评论
4个回答

24

好的。

因此,我认为您可以使用本地的dispatchEventaddEventListener方法,并将document作为唯一元素用于发布订阅这些事件。例如:

var myCustomEvent = new Event('someEvent');
document.dispatchEvent(myCustomEvent);
...
document.addEventListener('someEvent', doSomething, false);

而要做到跨浏览器,您可以:

var myCustomEvent = new Event('someEvent');
document.dispatchEvent(myCustomEvent);
...
if (document.addEventListener) {
    document.addEventListener('someEvent', doSomething, false);
} else {
    document.attachEvent('someEvent', doSomething);
}
您可以在这里这里阅读更多相关主题的内容。希望这可以帮到您。

2
我的问题是:是否有更好(或至少更标准)的方式来实现“广播”自定义事件的效果?
不,JavaScript中没有更标准的发布/订阅方式。它没有直接内置到语言或浏览器中,并且我所知道的也没有平台标准。
您有几个选项(大多数您似乎已经知道了),可以将自己的系统放在一起。
您可以选择特定的对象,例如document对象或window对象或您创建的新对象,并使用jQuery的.on().trigger()作为中央清算所,以拼凑出类似于发布/订阅的模型。您甚至可以通过将其编码到几个实用程序函数中来隐藏该对象的存在,如果您想要的话。
或者,正如您已经知道的那样,您可以使用jQuery.Callbacks功能。 jQuery doc中甚至有发布/订阅示例代码。
或者,您可以找到一个提供某种传统发布/订阅模型的第三方库。
或者,您可以从头开始构建自己的系统,这实际上只涉及到保持一个回调函数列表,这些函数与特定事件相关联,因此当触发该事件时,您可以调用每个回调函数。

0
如果您在这里寻找使用jQuery的方法,请看这里:
添加事件广播/分发代码:
语法:
$(<element-name>).trigger(<event-name>); 示例:
$.ajax({
    ...
    complete: function () {
        // signal to registered listeners that event has occured
        $(document).trigger("build_complete");
        ...
    }
});

为事件注册监听器:

语法:
$(<element-name>).on(<event-name>, function() {...});

示例:

$(document).on("build_complete", function () {
    NextTask.Init();
});

注意:采用以下方式:$(document).build_complete(function() {...}); 会导致错误:Uncaught TypeError: $(...).build_complete is not a function

0

我知道这个问题早在2015年就被标记为已回答 - 但是一个优雅而简单的解决方案可能是使用 Redux


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