如何使用 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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 22:39:43  来源:igfitidea点击:

How to use JavaScript EventTarget?

javascriptevents

提问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 EventTargetis 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

EventTargetis 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 EventTargetgood 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 EventTargetfor calling purposes, but some other native functions might. This is similar like creating elements, we have document.createElementfactory 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. EventTargetis one of them.

有些方法是不可调用的,但仍然可以作为对象的属性进行比较。因此它们是可见的。EventTarget是其中之一。

回答by guest

EventTargetis 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 EventTargetcan 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 windowor documentor any other existing DOM element to register listeneres and dispatch the event. EventTargetis not a object, it's an interface. Try accessing EventTargetin JavaScript console and you'll see that.

您需要使用windowdocument或任何其他现有 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 CustomEventbeing used this way, there is code for a polyfill listen on the MDN pageor a package on GitHuband npm

对于不支持CustomEvent这样使用的IE浏览器,MDN页面有polyfill监听的代码或者GitHubnpm的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 listenersproperty 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'))