在 JavaScript 中不使用 DOM 事件的自定义事件模型

问题描述 投票:0回答:8

我对 JavaScript 和编程不太熟悉,我对对象和事件有一些疑问。

说我有一个对象:

var computer = {
    keyboard: {}
}

我正在寻找一种将事件注册到键盘对象的方法:

computer.keyboard.registerEvent( "keyEscape" );

触发事件:

computer.keyboard.dispatchEvent( "keyEscape" );

并创建事件处理程序:

computer.keyboard.addEventListener( "keyEscape", function() {...} );

我知道如何使用 DOM 元素而不是对象来执行此操作。这是可以用 JavaScript 完成的事情吗(也许在 JQuery 的帮助下)?

即使是最轻微的指导也将不胜感激。

javascript events object handler
8个回答
75
投票

如果你想创建一个完全独立的事件系统而不依赖 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');

JSBin 现场直播


48
投票

您可以像某些人建议的那样简单地创建一个新的

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


47
投票

更新:此方法仅对旧浏览器有效。对于较新的浏览器,最好使用

EventTarget
类。详情请参阅Nadeem Douba 的回答

如果您不想实现自己的事件处理机制,您可能会喜欢我的方法。您将从通常的 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 片段,可以使用浏览器对其进行测试。


13
投票

这里有一点 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);
}

通过一些简单的命名空间,您将能够轻松地在模块之间传递基本事件!


5
投票

您可以使用 JQuery 来完成。

订阅您的自定义事件:

$(computer.keyboard).on('keyEscape', function(e){
    //Handler code
});

抛出您的自定义事件:

$(computer.keyboard).trigger('keyEscape', {keyCode:'Blah blah'});

可能不是最好的方法,但您也可以在方法中创建函数(addEventListener、dispatchEvent...)来包装 JQuery 逻辑,以支持本机外观的 api 和 JQuery。


0
投票

最有可能的是,您需要一个事件机制作为多个对象之间的通信媒介。

实现这一目标的方法如下:

/**
 * 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>


0
投票

自从我在近 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!'
});

-1
投票

这是 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>
© www.soinside.com 2019 - 2024. All rights reserved.