如何使用 JavaScript EventTarget?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22186467/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to use JavaScript EventTarget?
提问by Thank you
I would like to create a custom event emitter in my client-side programs. I am referencing this (sparse) documentation for EventTarget
我想在我的客户端程序中创建一个自定义事件发射器。我正在参考EventTarget 的这个(稀疏)文档
My implementation attempt
我的实现尝试
var Emitter = function Emitter() {
EventTarget.call(this);
};
Emitter.prototype = Object.create(EventTarget.prototype, {
constructor: {
value: Emitter
}
});
My desired usage
我想要的用法
var e = new Emitter();
e.addEventListener("hello", function() {
console.log("hello there!");
});
e.dispatchEvent(new Event("hello"));
// "hello there!"
Where it fails
失败的地方
var e = new Emitter();
// TypeError: Illegal constructor
What am I doing wrong?
我究竟做错了什么?
Update
更新
The following is possible, but it's a hack that depends on a dummy DOMElement
以下是可能的,但它是一个依赖于虚拟 DOMElement 的 hack
var fake = document.createElement("phony");
fake.addEventListener("hello", function() { console.log("hello there!"); });
fake.dispatchEvent(new Event("hello"));
// "hello there!"
I'd like to know how to do this without having to use the dummy element
我想知道如何在不使用虚拟元素的情况下做到这一点
回答by Thank you
I gave up on this awhile ago, but recently needed it again. Here's what I ended up using.
我之前放弃了这个,但最近又需要它了。这是我最终使用的。
ES6
ES6
class Emitter {
constructor() {
var delegate = document.createDocumentFragment();
[
'addEventListener',
'dispatchEvent',
'removeEventListener'
].forEach(f =>
this[f] = (...xs) => delegate[f](...xs)
)
}
}
// sample class to use Emitter
class Example extends Emitter {}
// run it
var e = new Example()
e.addEventListener('something', event => console.log(event))
e.dispatchEvent(new Event('something'))
ES5
ES5
function Emitter() {
var eventTarget = document.createDocumentFragment()
function delegate(method) {
this[method] = eventTarget[method].bind(eventTarget)
}
[
"addEventListener",
"dispatchEvent",
"removeEventListener"
].forEach(delegate, this)
}
// sample class to use it
function Example() {
Emitter.call(this)
}
// run it
var e = new Example()
e.addEventListener("something", function(event) {
console.log(event)
})
e.dispatchEvent(new Event("something"))
Yeah!
是的!
For those that need to support older versions of ecmascript, here you go
对于那些需要支持旧版本 ecmascript 的人,你去吧
// IE < 9 compatible
function Emitter() {
var eventTarget = document.createDocumentFragment();
function addEventListener(type, listener, useCapture, wantsUntrusted) {
return eventTarget.addEventListener(type, listener, useCapture, wantsUntrusted);
}
function dispatchEvent(event) {
return eventTarget.dispatchEvent(event);
}
function removeEventListener(type, listener, useCapture) {
return eventTarget.removeEventListener(type, listener, useCapture);
}
this.addEventListener = addEventListener;
this.dispatchEvent = dispatchEvent;
this.removeEventListener = removeEventListener;
}
The usage stays the same
用法保持不变
回答by Entity Black
Bergi was right about the part, that EventTarget
is just an interface and not a constructor.
Bergi 关于这部分是正确的,那EventTarget
只是一个接口而不是构造函数。
There are multiple objects in js that are valid event targets. As mentioned there:
Element, document, and window are the most common event targets, but there are also others for example Websocket
. Anyway, all of them are given.
js 中有多个对象是有效的事件目标。正如那里提到的:
元素、文档和窗口是最常见的事件目标,但也有其他的,例如Websocket
。反正都是给的。
If you make a short test, you can notice few things:
如果你做一个简短的测试,你会注意到一些事情:
EventTarget.isPrototypeOf(WebSocket); // true
var div = document.createElement("div");
EventTarget.isPrototypeOf(div.constructor); // true
typeof EventTarget // function
EventTarget() // TypeError: Illegal constructor
EventTarget
is prototype of these constructors, which is something you can't set for any other constructor (and even if you could, it wouldnt probably work). Also it is a function, but not callable one.
EventTarget
是这些构造函数的原型,这是您无法为任何其他构造函数设置的(即使可以,它也可能无法工作)。它也是一个函数,但不是可调用的。
Now this is the time when you ask:So what is it EventTarget
good for and how can I use it?
现在是你问的时候了:那么它有什么EventTarget
好处以及我该如何使用它?
We have 3 methods that each event emitter needs to implement and there was probably a need to bind these methods together, so we have an interface for them. Which means you can't use EventTarget
for calling purposes, but some other native functions might. This is similar like creating elements, we have document.createElement
factory method and we don't (can't) use new HTMLDivElement()
to create a new element, but we can compare constructors of two elements.
我们有每个事件发射器需要实现的 3 个方法,并且可能需要将这些方法绑定在一起,因此我们为它们提供了一个接口。这意味着您不能EventTarget
用于调用目的,但其他一些本机函数可能会使用。这类似于创建元素,我们有document.createElement
工厂方法,我们不(不能)new HTMLDivElement()
用来创建新元素,但我们可以比较两个元素的构造函数。
Conclusion
结论
If you want to create custom event emitter, you always have to create some dummy object or use some that already exists. From my point of view, it doesn't matter what object it will be.
如果你想创建自定义事件发射器,你总是必须创建一些虚拟对象或使用一些已经存在的对象。在我看来,它是什么对象并不重要。
Some methods are not callable, but still can be compared as properties of objects. Therefore they are visible. EventTarget
is one of them.
有些方法是不可调用的,但仍然可以作为对象的属性进行比较。因此它们是可见的。EventTarget
是其中之一。
回答by guest
EventTarget
is now specified as constructible in the DOM living standard. It is supported in Chrome 64(already out) and Firefox 59(coming March 13).
EventTarget
现在在DOM 生活标准中被指定为可构造的。它在支持Chrome浏览器64(已出)和火狐59(即将3月13日)。
回答by Neil
There are 3 ways to achieve this depending on browser support.
根据浏览器支持,有 3 种方法可以实现这一点。
1) EventTarget is now constructable, so just extend it:
1) EventTarget 现在是可构造的,所以只需扩展它:
class MyEventTarget extends EventTarget {
constructor(){
super()
}
}
2) The DOM 'Node' interface implements EventTarget, so just implement that instead:
2) DOM 'Node' 接口实现了 EventTarget,所以只需实现它:
function MyEventTarget(){
var target = document.createTextNode(null);
this.addEventListener = target.addEventListener.bind(target);
this.removeEventListener = target.removeEventListener.bind(target);
this.dispatchEvent = target.dispatchEvent.bind(target);
}
MyEventTarget.prototype = EventTarget.prototype;
3) Roll your own (assuming no options arg) & dispatch async:
3)推出自己的(假设没有选项arg)并调度异步:
function MyEventTarget(){
this.__events = new Map();
}
MyEventTarget.prototype = {
addEventListener(type, listener){
var listeners = this.__events.get(type);
if(!listeners){
listeners = new Set();
this.__events.set(type, listeners);
}
listeners.add(listener);
},
removeEventListener(type, listener){
var listeners = this.__events.get(type);
if(listeners){
listeners.delete(listener);
if(listeners.size === 0){
this.__events.delete(type);
}
}
},
dispatchEvent(event){
var listeners = this.__events.get(event.type);
if(listeners){
for(let listener of listeners){
setTimeout(listener.call(null, event), 0);
}
}
}
}
Replace Map()/Set() with {}/[] if required.
如果需要,将 Map()/Set() 替换为 {}/[]。
All 3 of these options can be tested with:
所有这 3 个选项都可以通过以下方式进行测试:
var target = new MyEventTarget();
target.addEventListener('test', (e) => {console.log(e.detail);}, false);
var event = new CustomEvent('test', {detail : 'My Test Event'});
target.dispatchEvent(event);
Any object that needs to implement your own 'EventTarget' interface can inherit it exactly as the native one does:
任何需要实现您自己的“EventTarget”接口的对象都可以像原生接口一样继承它:
function Person(name){
MyEventTarget.call(this);
this.__name = name;
}
Person.prototype = {
__proto__ : MyEventTarget.prototype,
get name(){ return this.__name;}
}
回答by Lauro Moraes
Without taking into consideration browser support where EventTarget
can not be instantiated as a constructor and only to enrich this issue with yet another functional example.
在不考虑浏览器支持的情况下EventTarget
,无法将其实例化为构造函数,而仅使用另一个功能示例来丰富此问题。
According to the compatibility list described by Mozillaitself in this date (October 7, 2018):
根据Mozilla本身在该日期(2018 年 10 月 7 日)描述的兼容性列表:
EventTarget(constructor):
事件目标(构造函数):
- desktop:
- Chrome 64
- Firefox 59
- Opera 51
- mobile:
- WebView 64
- Chrome Android 64
- Firefox Android 59
- Opera Android 51
- 桌面:
- 铬 64
- 火狐 59
- 歌剧51
- 手机:
- 网络视图 64
- 铬安卓 64
- 火狐安卓 59
- 歌剧安卓 51
Extends:
扩展:
class Emitter extends EventTarget {
constructor() {
super()
}
}
You could create common methods in many event plugins like: on()
, off()
, .once()
and emit()
(using CustomEvent
):
你可以创建许多事件的插件常用的方法,如:on()
,off()
,.once()
和emit()
(使用CustomEvent
):
/**
* Emmiter - Event Emitter
* @license The MIT License (MIT) - [https://github.com/subversivo58/Emitter/blob/master/LICENSE]
* @copyright Copyright (c) 2020 Lauro Moraes - [https://github.com/subversivo58]
* @version 0.1.0 [development stage] - [https://github.com/subversivo58/Emitter/blob/master/VERSIONING.md]
*/
const sticky = Symbol()
class Emitter extends EventTarget {
constructor() {
super()
// store listeners (by callback)
this.listeners = {
'*': [] // pre alocate for all (wildcard)
}
// l = listener, c = callback, e = event
this[sticky] = (l, c, e) => {
// dispatch for same "callback" listed (k)
l in this.listeners ? this.listeners[l].forEach(k => k === c ? k(e.detail) : null) : null
}
}
on(e, cb, once = false) {
// store one-by-one registered listeners
!this.listeners[e] ? this.listeners[e] = [cb] : this.listeners[e].push(cb);
// check `.once()` ... callback `CustomEvent`
once ? this.addEventListener(e, this[sticky].bind(this, e, cb), { once: true }) : this.addEventListener(e, this[sticky].bind(this, e, cb))
}
off(e, Fn = false) {
if ( this.listeners[e] ) {
// remove listener (include ".once()")
let removeListener = target => {
this.removeEventListener(e, target)
}
// use `.filter()` to remove expecific event(s) associated to this callback
const filter = () => {
this.listeners[e] = this.listeners[e].filter(val => val === Fn ? removeListener(val) : val);
// check number of listeners for this target ... remove target if empty
this.listeners[e].length === 0 ? e !== '*' ? delete this.listeners[e] : null : null
}
// use `while()` to iterate all listeners for this target
const iterate = () => {
let len = this.listeners[e].length;
while (len--) {
removeListener(this.listeners[e][len])
}
// remove all listeners references (callbacks) for this target (by target object)
e !== '*' ? delete this.listeners[e] : this.listeners[e] = []
}
Fn && typeof Fn === 'function' ? filter() : iterate()
}
}
emit(e, d) {
this.listeners['*'].length > 0 ? this.dispatchEvent(new CustomEvent('*', {detail: d})) : null;
this.dispatchEvent(new CustomEvent(e, {detail: d}))
}
once(e, cb) {
this.on(e, cb, true)
}
}
const MyEmitter = new Emitter()
// one or more listeners for same target ...
MyEmitter.on('xyz', data => {
console.log('first listener: ', data)
})
MyEmitter.on('xyz', data => {
console.log('second listener: ', data)
})
// fire event for this target
MyEmitter.emit('xyz', 'zzzzzzzzzz...') // see listeners show
// stop all listeners for this target
MyEmitter.off('xyz')
// try new "emit" listener event ?
MyEmitter.emit('xyz', 'bu bu bu') // nothing ;)
// fire a "once" ? Yes, fire
MyEmitter.once('abc', data => {
console.log('fired by "once": ', data)
})
// run
MyEmitter.emit('abc', 'Hello World') // its show listener only once
// test "once" again
MyEmitter.emit('abc', 'Hello World') // nothing
回答by noseratio
Here is how to do it using CustomEvent
, cross-browser (fiddle):
以下是使用CustomEvent
跨浏览器(小提琴)的方法:
// listen to event
window.addEventListener("say", function(e) { alert(e.detail.word); });
// create and dispatch the event
var event = document.createEvent("CustomEvent");
event.initCustomEvent('say', true, true,
{ "word": "Hello!" });
window.dispatchEvent(event);
You'd need to use window
or document
or any other existing DOM element to register listeneres and dispatch the event. EventTarget
is not a object, it's an interface. Try accessing EventTarget
in JavaScript console and you'll see that.
您需要使用window
或document
或任何其他现有 DOM 元素来注册侦听器并分派事件。EventTarget
不是一个对象,它是一个接口。尝试EventTarget
在 JavaScript 控制台中访问,您会看到。
回答by Codebling
EventType()
constructor is now supported in most modern browsers.
EventType()
现在大多数现代浏览器都支持构造函数。
For the browsers which still do not support it, there is a polyfillavailable.
对于仍然不支持它的浏览器,有一个polyfill可用。
This means that it's as simple as:
这意味着它很简单:
var e = new EventTarget();
e.addEventListener("hello", function() {
console.log("hello there!");
});
e.dispatchEvent(new CustomEvent("hello"));
// "hello there!"
For Internet Explorer, which doesn't support CustomEvent
being used this way, there is code for a polyfill listen on the MDN pageor a package on GitHuband npm
对于不支持CustomEvent
这样使用的IE浏览器,MDN页面有polyfill监听的代码或者GitHub和npm上的package
For the sake of completeness, in Node or an Electron app you would do
为了完整起见,在 Node 或 Electron 应用程序中,您会这样做
var EventEmitter = require('events');
var e = new EventEmitter();
e.addListener("hello", function() {
console.log("hello there!");
});
e.emit("hello")
// "hello there!"
回答by Lewis
Try my simple ES6 implemetation.
试试我的简单 ES6 实现。
class DOMEventTarget {
constructor() {
this.listeners = new Map();
}
addEventListener(type, listener) {
this.listeners.set(listener.bind(this), {
type, listener
});
}
removeEventListener(type, listener) {
for(let [key, value] of this.listeners){
if(value.type !== type || listener !== value.listener){
continue;
}
this.listeners.delete(key);
}
}
dispatchEvent(event) {
Object.defineProperty(event, 'target',{value: this});
this['on' + event.type] && this['on' + event.type](event);
for (let [key, value] of this.listeners) {
if (value.type !== event.type) {
continue;
}
key(event);
}
}
}
let eventEmitter = new DOMEventTarget();
eventEmitter.addEventListener('test', e => {
console.log('addEventListener works');
});
eventEmitter.ontest = e => console.log('ontype works');
eventEmitter.dispatchEvent(new Event('test'));
回答by Ini
There are two ways to implement the EventTarget "Interface".
有两种方法可以实现 EventTarget“接口”。
1) Like mdn suggestsuse javascript prototypes. In my opinion this is clearly not the best approachto do this. The simple reason is that everybody who does use your library has to know that he needs to add a listeners
property to his constructor function.
1) 像mdn 建议使用javascript 原型。在我看来,这显然不是做到这一点的最佳方法。原因很简单,每个使用你的库的人都必须知道他需要listeners
向他的构造函数添加一个属性。
function implement_event_target_interface(target_constructor_function)
{
target_constructor_function.prototype.listeners = null;
target_constructor_function.prototype.addEventListener = function(type, callback) {
if (!(type in this.listeners)) {
this.listeners[type] = [];
}
this.listeners[type].push(callback);
};
target_constructor_function.prototype.removeEventListener = function(type, callback) {
if (!(type in this.listeners)) {
return;
}
var stack = this.listeners[type];
for (var i = 0, l = stack.length; i < l; i++) {
if (stack[i] === callback){
stack.splice(i, 1);
return;
}
}
};
target_constructor_function.prototype.dispatchEvent = function(event) {
if (!(event.type in this.listeners)) {
return true;
}
var stack = this.listeners[event.type].slice();
for (var i = 0, l = stack.length; i < l; i++) {
stack[i].call(this, event);
}
return !event.defaultPrevented;
};
}
let Person = function()
{
this.listeners = {}; // Every contructor that implements the event_target_interface must have this property. This is not very practical and intuitive for the library-user.
this.send_event = function() {
var event = new CustomEvent('test_event', { 'detail': "test_detail" });
this.dispatchEvent(event);
}
}
implement_event_target_interface(Person);
let person = new Person();
person.addEventListener('test_event', function (e) {
console.log("catched test_event from person")
}.bind(this), false);
person.send_event();
And not only that, it gets even worse when you use constructor inheritance on Person
, because you also need to inherit the prototype in order to be able to send events.
不仅如此,当您在 上使用构造函数继承时,情况会变得更糟Person
,因为您还需要继承原型才能发送事件。
let Student = function() {
Person.call(this);
}
Student.prototype = Person.prototype;
Student.prototype.constructor = Student;
let student = new Student();
student.addEventListener('test_event', function (e) {
console.log("catched test_event from student")
}.bind(this), false);
student.send_event();
2) Use constructor inheritance. Much much better.
2) 使用构造函数继承。好多了。
function EventTarget()
{
this.listeners = {};
this.addEventListener = function(type, callback) {
if (!(type in this.listeners)) {
this.listeners[type] = [];
}
this.listeners[type].push(callback);
};
this.removeEventListener = function(type, callback) {
if (!(type in this.listeners)) {
return;
}
var stack = this.listeners[type];
for (var i = 0, l = stack.length; i < l; i++) {
if (stack[i] === callback){
stack.splice(i, 1);
return;
}
}
};
this.dispatchEvent = function(event) {
if (!(event.type in this.listeners)) {
return true;
}
var stack = this.listeners[event.type].slice();
for (var i = 0, l = stack.length; i < l; i++) {
stack[i].call(this, event);
}
return !event.defaultPrevented;
};
}
let Person = function()
{
EventTarget.call(this);
this.send_event = function() {
var event = new CustomEvent('test_event', { 'detail': "test_detail" });
this.dispatchEvent(event);
}
}
let person = new Person();
person.addEventListener('test_event', function (e) {
console.log("catched test_event from person")
}.bind(this), false);
person.send_event();
回答by Imamudin Naseem
sample code snippet to use javascript EventTarget
使用 javascript EventTarget 的示例代码片段
// attach event
var ev = EventTarget.prototype.addEventListener.call(null, 'alert', () => alert('ALERTED'))
// dispatch event
ev.dispatchEvent.call(null, new Event('alert'))
// attach event
var ev = EventTarget.prototype.addEventListener.call(null, 'alert', () => alert('ALERTED'))
// dispatch event
ev.dispatchEvent.call(null, new Event('alert'))