我想在客户端程序中创建自定义事件发射器。我正在引用EventTarget
的这个(稀疏)文档我的实施尝试
var Emitter = function Emitter() {
EventTarget.call(this);
};
Emitter.prototype = Object.create(EventTarget.prototype, {
constructor: {
value: Emitter
}
});
我想要的用法
var e = new Emitter();
e.addEventListener("hello", function() {
console.log("hello there!");
});
e.dispatchEvent(new Event("hello"));
// "hello there!"
失败的地方
var e = new Emitter();
// TypeError: Illegal constructor
我做错了什么?
更新
以下是可能的,但这是一个依赖于虚拟DOMElement
的黑客攻击var fake = document.createElement("phony");
fake.addEventListener("hello", function() { console.log("hello there!"); });
fake.dispatchEvent(new Event("hello"));
// "hello there!"
我想知道如何在不使用虚拟元素的情况下执行此操作
答案 0 :(得分:31)
我前一段时间放弃了这一点,但最近再次需要它。这就是我最终使用的内容。
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
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"))
呀!
对于那些需要支持旧版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;
}
用法保持不变
答案 1 :(得分:8)
Bergi对于该部分是正确的,EventTarget
只是一个接口而不是构造函数。
js中有多个对象是有效的事件目标。如上所述there:
元素,文档和窗口是最常见的事件目标,但也有其他例如Websocket
。无论如何,所有这些都给出了。
如果你做了一个简短的测试,你会发现一些事情:
EventTarget.isPrototypeOf(WebSocket); // true
var div = document.createElement("div");
EventTarget.isPrototypeOf(div.constructor); // true
typeof EventTarget // function
EventTarget() // TypeError: Illegal constructor
EventTarget
是这些构造函数的原型,这是你不能为任何其他构造函数设置的东西(即使你可以,它也不会起作用)。它也是一个功能,但不是可调用的功能。
现在这是你问的时候:那么EventTarget
有什么用呢?我该怎么用呢?
我们有3个方法,每个事件发射器需要实现,并且可能需要将这些方法绑定在一起,因此我们有一个接口。这意味着您不能将EventTarget
用于调用目的,但其他一些本机函数可能会。这类似于创建元素,我们有document.createElement
工厂方法,我们不(不能)使用new HTMLDivElement()
来创建新元素,但我们可以比较两个元素的构造函数。
<强>结论强>
如果要创建自定义事件发射器,则必须始终创建一些虚拟对象或使用已存在的对象。从我的观点来看,它将是什么对象并不重要。
某些方法不可调用,但仍可以作为对象的属性进行比较。因此它们是可见的。 EventTarget
就是其中之一。
答案 2 :(得分:6)
EventTarget
现在在DOM living standard中被指定为可构造的。它由Chrome 64(已经出局)和Firefox 59(即将于3月13日)支持。
答案 3 :(得分:2)
有3种方法可以实现此目的,具体取决于浏览器的支持情况。
1)EventTarget现在是可构造的,因此只需对其进行扩展即可:
class MyEventTarget extends EventTarget {
constructor(){
super()
}
}
2)DOM“节点”接口实现了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)自己滚动(假设没有选项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);
}
}
}
}
如果需要,用{} / []替换Map()/ Set()。
所有这三个选项均可通过以下方式进行测试:
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);
任何需要实现自己的“ EventTarget”接口的对象都可以像本机对象一样完全继承它:
function Person(name){
MyEventTarget.call(this);
this.__name = name;
}
Person.prototype = {
__proto__ : MyEventTarget.prototype,
get name(){ return this.__name;}
}
答案 4 :(得分:1)
以下是使用CustomEvent
,跨浏览器(fiddle)执行此操作的方法:
// 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);
您需要使用window
或document
或任何其他现有DOM元素来注册侦听并分派事件。 EventTarget
不是对象,它是一个接口。尝试在JavaScript控制台中访问EventTarget
,您会看到。
答案 5 :(得分:1)
在不考虑浏览器支持的情况下,EventTarget
不能实例化为构造函数,而只是通过另一个功能示例来丰富此问题。
根据Mozilla本身在此日期(2018年10月7日)中描述的兼容性列表:
EventTarget (构造函数):
扩展:
class Emitter extends EventTarget {
constructor() {
super()
}
}
您可以在许多事件插件中创建通用方法,例如:on()
,off()
,.once()
和emit()
(使用CustomEvent
):
// copyright, license and more examples see: https://github.com/subversivo58/Emitter
class Emitter extends EventTarget {
constructor() {
super()
// store listeners
this.listeners = {}
}
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, cb, { once: true }) : this.addEventListener(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 => {
return val === Fn ? removeListener(val) : val
})
// check number of listeners for this target ... remove target if empty
if ( this.listeners[e].length === 0 ) {
delete this.listeners[e]
}
}
// use `.forEach()` to iterate all listeners for this target
const iterate = () => {
this.listeners[e].forEach((val, index, array) => {
removeListener(val)
// on end "loop" remove all listeners reference for this target (by target object)
if ( index === array.length -1 ) {
delete this.listeners[e]
}
})
}
Fn && typeof Fn === 'function' ? filter() : iterate()
}
}
emit(e, d) {
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 => {
// yep, date is a `CustomEvent` object so use the "detail" property for get data
console.log('first listener: ', data.detail)
})
MyEmitter.on('xyz', data => {
// yep, date is a `CustomEvent` object so use the "detail" property for get data
console.log('second listener: ', data.detail)
})
// 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.detail)
})
// run
MyEmitter.emit('abc', 'Hello World') // its show listener only once
// test "once" again
MyEmitter.emit('abc', 'Hello World') // nothing
请注意,return是CustomEvent
对象,因此,要实际获取数据,必须使用“详细信息”属性:
MyEmitter.on('target-event', data => {
console.log(data.detail)
})
答案 6 :(得分:1)
{
"message": "The given data was invalid.",
"errors": {
"name": [
"The name field is required."
],
"email": [
"The email has already been taken."
]
}
}
的构造函数现在为supported in most modern browsers。
对于仍不支持它的浏览器,有polyfill可用。
这意味着它很简单:
const express = require('express');
const exphbs = require('express-handlebars');
const app = express();
app.engine('handlebars', exphbs({ defaultLayout: 'main' }));
app.set('view engine', 'handlebars');
app.use(express.static('public'));
app.get('/', (req, res) => {
res.render('home', { title: 'Home Page' });
});
app.get('/about', (req, res) => {
res.render('about', { title: 'About Us Page' });
});
app.listen(3000, () => console.log('server run!'));
对于不支持以这种方式使用EventType()
的Internet Explorer,有code for a polyfill listen on the MDN page或软件包on GitHub和npm
为了完整起见,在Node或Electron应用程序中,您应该这样做
var e = new EventTarget();
e.addEventListener("hello", function() {
console.log("hello there!");
});
e.dispatchEvent(new CustomEvent("hello"));
// "hello there!"
答案 7 :(得分:0)
尝试我的简单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'));
答案 8 :(得分:-1)
有两种方法可以实现EventTarget“接口”。
1)像mdn suggests一样使用 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();
不仅如此,当您在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)使用构造函数继承。 好得多。
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();
答案 9 :(得分:-2)
使用javascript EventTarget的示例代码段
// attach event
var ev = EventTarget.prototype.addEventListener.call(null, 'alert', () => alert('ALERTED'))
// dispatch event
ev.dispatchEvent.call(null, new Event('alert'))