如何使用JavaScript EventTarget?

Tha*_*you 25 javascript events

我想在我的客户端程序中创建一个自定义事件发射器.我正在引用EventTarget的这个(稀疏)文档

我的实施尝试

var Emitter = function Emitter() {
  EventTarget.call(this);
};

Emitter.prototype = Object.create(EventTarget.prototype, {
  constructor: {
    value: Emitter
  }
});
Run Code Online (Sandbox Code Playgroud)

我想要的用法

var e = new Emitter();

e.addEventListener("hello", function() {
  console.log("hello there!");
});

e.dispatchEvent(new Event("hello"));
// "hello there!"
Run Code Online (Sandbox Code Playgroud)

哪里失败了

var e = new Emitter();
// TypeError: Illegal constructor
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?


更新

以下是可能的,但它是一个依赖于虚拟DOMElement的hack

var fake = document.createElement("phony");
fake.addEventListener("hello", function() { console.log("hello there!"); });
fake.dispatchEvent(new Event("hello"));
// "hello there!"
Run Code Online (Sandbox Code Playgroud)

我想知道如何在不使用虚拟元素的情况下完成此操作

Tha*_*you 33

我不久前放弃了这一点,但最近再次需要它.这就是我最终使用的内容.

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'))
Run Code Online (Sandbox Code Playgroud)


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"))
Run Code Online (Sandbox Code Playgroud)

是啊!


对于那些需要支持旧版本的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;
}
Run Code Online (Sandbox Code Playgroud)

用法保持不变

  • 如果您使用此解决方案,请记住回调中的关键字“this”指向“DocumentFragment”而不是您的对象,如果您需要避免此类行为,请使用“bind”方法进行回调,如“e.addEventListener” (“某事”, callback.bind(e));` (2认同)

Ent*_*ack 8

Bergi对于该部分是正确的,这EventTarget只是一个接口而不是构造函数.

js中有多个对象是有效的事件目标.如前所述: 元素,文档,窗口是最常见的事件的目标,但也有其他例如Websocket.无论如何,所有这些都给出了.

如果你做一个简短的测试,你会发现一些事情:

EventTarget.isPrototypeOf(WebSocket); // true

var div = document.createElement("div");

EventTarget.isPrototypeOf(div.constructor); // true

typeof EventTarget // function

EventTarget() // TypeError: Illegal constructor
Run Code Online (Sandbox Code Playgroud)

EventTarget是这些构造函数的原型,这是你不能为任何其他构造函数设置的东西(即使你可以,它也可能不起作用).它也是一个功能,但不是可调用的功能.

现在这是你问的时候:那有什么EventTarget好处,我该如何使用呢?

我们有3个方法,每个事件发射器需要实现,并且可能需要将这些方法绑定在一起,因此我们有一个接口.这意味着您不能EventTarget用于调用目的,但其他一些本机功能可能会.这类似于创建元素,我们有document.createElement工厂方法,我们不(不能)new HTMLDivElement()用来创建新元素,但我们可以比较两个元素的构造函数.

结论

如果要创建自定义事件发射器,则始终必须创建一些虚拟对象或使用已存在的虚拟对象.从我的观点来看,它将是什么对象并不重要.

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


Nei*_*eil 8

根据浏览器支持的不同,有 3 种方法可以实现此目的。

1) EventTarget 现在是可构造的,所以只需扩展它:

class MyEventTarget extends EventTarget {
    constructor(){
        super()
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

3)自己动手(假设没有选项参数)并调度异步:

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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果需要,请将 Map()/Set() 替换为 {}/[]。

所有这 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);
Run Code Online (Sandbox Code Playgroud)

任何需要实现您自己的“EventTarget”接口的对象都可以像本机对象一样继承它:

function Person(name){
    MyEventTarget.call(this);
    this.__name = name;
}
Person.prototype = {
    __proto__ : MyEventTarget.prototype,

    get name(){ return this.__name;}
}
Run Code Online (Sandbox Code Playgroud)


gue*_*est 6

EventTarget现在已在DOM生活标准中指定为可构造。它在支持Chrome浏览器64(已出)和火狐59(即将3月13日)。

  • v8 中的快速检查显示 `e = Object.create(new EventTarget()); e.dispatchEvent(new Event('click'));` 会产生“调用错误”,因此它仍然不可继承。 (2认同)

Lau*_*aes 5

在不考虑浏览器支持的情况下,EventTarget无法将其实例化为构造函数,而只是通过另一个功能示例来丰富此问题。

根据Mozilla自己在该日期(2018 年 10 月 7 日)描述的兼容性列表:

事件目标(构造函数):

  • 桌面
    • 铬64
    • 火狐59
    • 歌剧 51
  • 移动的
    • 网页视图 64
    • Chrome 安卓 64
    • 火狐安卓 59
    • 歌剧安卓 51

延伸:

class Emitter extends EventTarget {
    constructor() {
        super()
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以在许多事件插件中创建通用方法,例如:、 和on()off()使用):.once()emit()CustomEvent

class Emitter extends EventTarget {
    constructor() {
        super()
    }
}
Run Code Online (Sandbox Code Playgroud)


Cod*_*ing 5

EventTarget()现在大多数现代浏览器都支持构造函数。

对于仍然不支持它的浏览器,有一个polyfill可用。

这意味着它很简单:

var e = new EventTarget();

e.addEventListener("hello", function() {
  console.log("hello there!");
});

e.dispatchEvent(new CustomEvent("hello"));
// "hello there!"
Run Code Online (Sandbox Code Playgroud)

为了完整起见,在 Node 或 Electron 应用程序中你会这样做

var EventEmitter = require('events');

var e = new EventEmitter();

e.addListener("hello", function() {
  console.log("hello there!");
});

e.emit("hello")
// "hello there!"

Run Code Online (Sandbox Code Playgroud)