自定义事件模型,无需在JavaScript中使用DOM事件
Custom Events Model without using DOM events in JavaScript
我是JavaScript和编程新手,我有一些关于对象和事件的问题。
假设我有一个对象:
var computer = {
keyboard: {}
}
我正在寻找的是一种将事件注册到键盘对象的方法:
computer.keyboard.registerEvent( "keyEscape" );
触发事件:
computer.keyboard.dispatchEvent( "keyEscape" );
并创建事件处理程序:
computer.keyboard.addEventListener( "keyEscape", function() {...} );
我知道如何使用DOM元素而不是对象来做到这一点。这是可以在JavaScript(也许与JQuery的帮助)中完成的事情吗?
如果你想创建一个完全独立的事件系统,而不依赖于DOM事件,你可以使用反应器模式
function Event(name){
this.name = name;
this.callbacks = [];
}
Event.prototype.registerCallback = function(callback){
this.callbacks.push(callback);
}
function Reactor(){
this.events = {};
}
Reactor.prototype.registerEvent = function(eventName){
var event = new Event(eventName);
this.events[eventName] = event;
};
Reactor.prototype.dispatchEvent = function(eventName, eventArgs){
this.events[eventName].callbacks.forEach(function(callback){
callback(eventArgs);
});
};
Reactor.prototype.addEventListener = function(eventName, callback){
this.events[eventName].registerCallback(callback);
};
像使用DOM事件模型一样使用
var reactor = new Reactor();
reactor.registerEvent('big bang');
reactor.addEventListener('big bang', function(){
console.log('This is big bang listener yo!');
});
reactor.addEventListener('big bang', function(){
console.log('This is another big bang listener yo!');
});
reactor.dispatchEvent('big bang');
Live at JSBin
如果您不想实现自己的事件处理机制,您可能会喜欢我的方法。你可以从通常的DOM事件中获得所有的特性(例如preventDefault()),而且我认为它更轻量级,因为它使用了浏览器已经实现的DOM事件处理能力。
只需在对象的构造函数中创建一个普通的DOM EventTarget对象,并将所有EventTarget接口调用传递给DOM EventTarget对象:
var MyEventTarget = function(options) {
// Create a DOM EventTarget object
var target = document.createTextNode(null);
// Pass EventTarget interface calls to DOM EventTarget object
this.addEventListener = target.addEventListener.bind(target);
this.removeEventListener = target.removeEventListener.bind(target);
this.dispatchEvent = target.dispatchEvent.bind(target);
// Room your your constructor code
}
// Create an instance of your event target
myTarget = new MyEventTarget();
// Add an event listener to your event target
myTarget.addEventListener("myevent", function(){alert("hello")});
// Dispatch an event from your event target
var evt = new Event('myevent');
myTarget.dispatchEvent(evt);
还有一个JSFiddle代码片段可以在浏览器中进行测试。
您可以简单地创建一个新的EventTarget
实例,就像有些人建议的那样,而不必创建一个DOM对象,像这样:
const target = new EventTarget();
target.addEventListener('customEvent', console.log);
target.dispatchEvent(new Event('customEvent'));
这提供了您习惯使用DOM事件的所有功能,并且不需要创建空文档元素或节点。
查看Mozilla开发者指南获取更多信息:https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
这里有一点Necroposting,但是我昨晚刚刚写了一些像这样的东西-超级简单,并且基于Backbone.js事件模块:
EventDispatcher = {
events: {},
on: function(event, callback) {
var handlers = this.events[event] || [];
handlers.push(callback);
this.events[event] = handlers;
},
trigger: function(event, data) {
var handlers = this.events[event];
if (!handlers || handlers.length < 1)
return;
[].forEach.call(handlers, function(handler){
handler(data);
});
}
};
这种方法非常简单且可扩展,如果需要,可以在此基础上构建更复杂的事件系统。
使用EventDispatcher
非常简单:
function initializeListeners() {
EventDispatcher.on('fire', fire); // fire.bind(this) -- if necessary
}
function fire(x) {
console.log(x);
}
function thingHappened(thing) {
EventDispatcher.trigger('fire', thing);
}
使用一些简单的命名空间,您将能够轻松地在模块之间传递基本事件!
可以使用JQuery。
订阅自定义事件:
$(computer.keyboard).on('keyEscape', function(e){
//Handler code
});
抛出自定义事件:
$(computer.keyboard).trigger('keyEscape', {keyCode:'Blah blah'});
可能不是最好的方法,但你也可以在你的方法(addEventListener, dispatchEvent,…)中创建函数来包装JQuery逻辑,以支持原生api和JQuery。
最可能的情况是,您需要一个事件机制作为多个对象之间的通信媒介。
你可以这样做:
/**
* EventfulObject constructor/base.
* @type EventfulObject_L7.EventfulObjectConstructor|Function
*/
var EventfulObject = function() {
/**
* Map from event name to a list of subscribers.
* @type Object
*/
var event = {};
/**
* List of all instances of the EventfulObject type.
* @type Array
*/
var instances = [];
/**
* @returns {EventfulObject_L1.EventfulObjectConstructor} An `EventfulObject`.
*/
var EventfulObjectConstructor = function() {
instances.push(this);
};
EventfulObjectConstructor.prototype = {
/**
* Broadcasts an event of the given name.
* All instances that wish to receive a broadcast must implement the `receiveBroadcast` method, the event that is being broadcast will be passed to the implementation.
* @param {String} name Event name.
* @returns {undefined}
*/
broadcast: function(name) {
instances.forEach(function(instance) {
(instance.hasOwnProperty("receiveBroadcast") && typeof instance["receiveBroadcast"] === "function") &&
instance["receiveBroadcast"](name);
});
},
/**
* Emits an event of the given name only to instances that are subscribed to it.
* @param {String} name Event name.
* @returns {undefined}
*/
emit: function(name) {
event.hasOwnProperty(name) && event[name].forEach(function(subscription) {
subscription.process.call(subscription.context);
});
},
/**
* Registers the given action as a listener to the named event.
* This method will first create an event identified by the given name if one does not exist already.
* @param {String} name Event name.
* @param {Function} action Listener.
* @returns {Function} A deregistration function for this listener.
*/
on: function(name, action) {
event.hasOwnProperty(name) || (event[name] = []);
event[name].push({
context: this,
process: action
});
var subscriptionIndex = event[name].length - 1;
return function() {
event[name].splice(subscriptionIndex, 1);
};
}
};
return EventfulObjectConstructor;
}();
var Model = function(id) {
EventfulObject.call(this);
this.id = id;
this.receiveBroadcast = function(name) {
console.log("I smell another " + name + "; and I'm model " + this.id);
};
};
Model.prototype = Object.create(EventfulObject.prototype);
Model.prototype.constructor = Model;
// ---------- TEST AND USAGE (hopefully it's clear enough...)
// ---------- note: I'm not testing event deregistration.
var ob1 = new EventfulObject();
ob1.on("crap", function() {
console.log("Speaking about craps on a broadcast? - Count me out!");
});
var model1 = new Model(1);
var model2 = new Model(2);
model2.on("bust", function() {
console.log("I'm model2 and I'm busting!");
});
var ob2 = new EventfulObject();
ob2.on("bust", function() {
console.log("I'm ob2 - busted!!!");
});
ob2.receiveBroadcast = function() {
console.log("If it zips, I'll catch it. - That's me ob2.");
};
console.log("start:BROADCAST'n---------------");
model1.broadcast("crap");
console.log("end :BROADCAST'n---------------'n-'n-'n");
console.log("start:EMIT'n---------------");
ob1.emit("bust");
console.log("end:EMIT'n---------------");
<h1>...THE SHOW IS ON YOUR CONSOLE!</h1>
自从我在近10年后遇到这个问题以来,浏览器/javascript世界已经发生了很大的变化。
对于那些使用Javascript模块导入/导出的人,这里有我的两点建议:
// eventbus.js
class EventBus {
constructor() {
this.events = {};
}
on(type, callback) {
if (!this.events[type]) {
this.events[type] = [];
}
this.events[type].push(callback);
}
off(type, callback) {
if (!this.events[type]) {
return;
}
this.events[type] = this.events[type].filter(listener => listener !== callback);
}
dispatch(type, data) {
if (!this.events[type]) {
return;
}
this.events[type].forEach(listener => listener(data));
}
}
export const eventbus = new EventBus();
// somefile.js
import {eventbus} from './eventbus';
// Somewhere in a method/click callback/etc..
eventbus.dispatch('fire', {message: 'Fire in the hole!'});
// otherfile.js
import {eventbus} from './eventbus';
eventbus.on('fire', data => {
console.log(data.message); // logs 'Fire in the hole!'
});
下面是Mohsen的回答的简单扩展,作为一个清晰而简短的例子。
他的所有React函数都被封装到一个React()
中,添加了一个removeEventListener()
函数,整个示例以一个HTML文件的形式呈现(或在JSFiddle上查看)。
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>JS Bin</title>
<!--https://jsfiddle.net/romleon/qs26o3p8/-->
</head>
<body>
<script>
function Reactor() {
function Event(name) {
this.name = name;
this.callbacks = [];
}
Event.prototype.registerCallback = function(callback) {
this.callbacks.push(callback);
};
Event.prototype.unregisterCallback = function(callback) {
var array = this.callbacks,
index = array.indexOf(callback);
if (index > -1)
array.splice(index, 1);
}
this.events = {};
this.registerEvent = function(eventName) {
var event = new Event(eventName);
this.events[eventName] = event;
};
this.dispatchEvent = function(eventName, eventArgs) {
var events = this.events
if (events[eventName]) {
events[eventName].callbacks.forEach(function(callback) {
callback(eventArgs);
});
}
else
console.error("WARNING: can't dispatch " + '"' + eventName + '"')
};
this.addEventListener = function(eventName, callback) {
this.events[eventName].registerCallback(callback);
};
this.removeEventListener = function(eventName, callback) {
var events = this.events
if (events[eventName]) {
events[eventName].unregisterCallback(callback);
delete events[eventName];
}
else
console.error("ERROR: can't delete " + '"' + eventName + '"')
};
}
/*
demo of creating
*/
var reactor = new Reactor();
reactor.registerEvent('big bang');
reactor.registerEvent('second bang');
/*
demo of using
*/
log("-- add 2 event's listeners for 'big bang' and 1 for 'second bang'")
var callback1 = function() {
log('This is big bang listener')
}
reactor.addEventListener('big bang', callback1);
reactor.addEventListener('big bang', function() {
log('This is another big bang listener')
});
reactor.addEventListener('second bang', function() {
log('This is second bang!')
});
log("-- dipatch 'big bang' and 'second bang'")
reactor.dispatchEvent('big bang');
reactor.dispatchEvent('second bang');
log("-- remove first listener (with callback1)")
reactor.removeEventListener('big bang', callback1);
log("-- dipatch 'big bang' and 'second bang' again")
reactor.dispatchEvent('big bang');
reactor.dispatchEvent('second bang');
function log(txt) {
document.body.innerHTML += txt + '<br/>'
console.log(txt)
}
</script>
</body>
</html>
- DOM事件通过JSON转换为java
- 从dom中删除任何元素后,Touchmove事件停止触发
- IE9-添加和删除DOM元素会破坏父keydown事件
- 调用DOM提交方法时未激发jQuery提交事件
- 将Backbone.View重新注入DOM,保留事件而不是创建新事件
- 停止事件以在DOM中弹出
- 直接在DOM事件处理程序中调用作用域函数的最短方法是什么
- Meteor - 从另一个模板事件处理程序访问 DOM 元素
- 触发具有较低Z索引值的DOM元素的事件
- 动态创建的DOM元素上的jQuery事件绑定
- Angular JS根据搜索结果和点击事件更新DOM元素
- 在 JavaScript 中迭代 DOM 时关闭标记事件
- 如何克隆或重新调度 DOM 事件
- 当我认为它不应该重新渲染视图时,如何防止 Meteor 在 html 选择 dom 单击事件上重新渲染视图
- 测试 JavaScript Zombie 事件处理程序或 DOM 元素
- 如何将 DOM 元素绑定到自定义 $.touchpress 事件
- 绘制DOM元素:鼠标事件会被背景元素混淆
- DOM事件链接不起作用
- 禁用Javascript事件DOM中的Function
- 在之前(而不是之后)触发的事件DOM 元素在 javascript 中滚动