Js-based event management (subscribe – publish) —event-mange

About the event

When we develop in javascript, we often use a lot of events, like clicks, keyboards, mice, and so on, physical events. And what we’re talking about today, what I call events, is another form of event, subscription, publication, also known as the observer pattern, which defines a one-to-many dependency, where when an object’s state changes, all objects that depend on it get notified, whereas in javascript, It is customary to replace the publish-subscribe model with an event model.

Let me give you an example from life to help you understand this pattern. Hot summer, my mother cooked a meal on the table, steaming hot, then my mother called Xiaoming to eat (Xiaoming in the next room hungry to eat chicken at night…) “Xiao Ming came out and said to his mother,” Wait a while, the rice is cold, “then call me, too hot… Ten minutes later… Mother called you ‘rice is cold’, come to eat, and then Xiao Ming heard her mother’s shout said ‘rice is cold’, they quickly came out to eat. This example is the subscription-publish model described above. In this case, Xiao Ming is the subscriber (subscribing to ‘cold food’) and her mother is the publisher (publishing the signal ‘cold food’).

Using subscriptions – the publishing model has obvious advantages: subscribers don’t have to ask publishers if their meal is cold every second, at the right point in time, publishers will notify subscribers that their meal is cold and they can come and eat. In this way, xiaoming and her mother do not need to be coupled together, when xiaoming’s younger brother and sister want to eat in the meal cold, just tell her mother. There’s a subscription that every reader will no doubt be familiar with: publishing: binding DOM events

document.body.addEventListener('click'.function (e) {
     console.log('I executed... ')},false)
Copy the code

Back to business:

event-mangeThrough the subscription-publish model

Step by step

The main methods of event-Mange module are:

  • On: Subscriber, add event
  • Emit: Publisher, go event
  • Once: subscriber that adds events that can only be listened on once and then expire
  • RemoveListener: Remove a single subscription (event)
  • RemoveAllListener: Deletes a subscription for a single event type or all subscriptions
  • GetListenerCount: Get the number of subscribers

The main attributes of the event-Mange module are:

  • MaxEventListNum: Sets the maximum number of subscribers for a single event (default: 10)

Basic skeleton

First, we want to subscribe to and publish events via event.on and event.emit. We want to create an event instance through the constructor, and on and emit are the two methods of this instance.

function events () {};

// Enumerate the methods to the event object we want to implement

event.prototype.on = function () {};

event.prototype.emit = function () {};

event.prototype.once = function () {};

event.prototype.removeListener = function () {};

event.prototype.removeAllListener = function () {};

event.prototype.getListenerCount = function () {};
Copy the code

It looks like something’s missing, yeah, the Event object that we listed above, the MaxEventListNum property, let’s fill it in, okay

function event () {
    // Because the MaxEventListNum attribute can be set by the developer
    // So in the absence of set, we set it to undefind
    this.MaxEventListNum = this.MaxEventListNum || undefined;

    // If set is not set, we cannot make the number of listeners infinite
    // This may cause memory overflow
    // So we set the default value to 10 (of course, we can set it to anything else)
    this.defaultMaxEventListNum = 10;
}

Copy the code

Here, basically we want to achieve the time management module attributes and methods of the initial state is about the same, that is to say, the skeleton out, we need to fill his code logic, let him become flesh and blood (looks like a life…)

It’s worth thinking, once we’ve built the skeleton, what we’re going to do is a subscription-publish model, how are we going to remember a lot of subscription events? First of all, for a subscription, we need to have a subscription type, which is topic, and for that topic we need to put all the events that subscribe to that topic together, yes, we can choose Array, a preliminary construct

event_list: {
    topic1: [fn1, fn2, fn3 ...] . }Copy the code

So let’s add the event_list that holds our event to the code as an event attribute

function event () {
    // Here we make a simple judgment to avoid some unexpected errors
    if(!this.event_list) {
        this.event_list = {};
    }

    this.MaxEventListNum = this.MaxEventListNum || undefined;
    this.defaultMaxEventListNum = 10;
}
Copy the code

On method implementation
event.prototype.on = function () {};
Copy the code

The analysis shows that the ON method should first receive a subscribed topic, followed by a callback method that is triggered when the topic responds

event.prototype.on = function (eventName, content) {};
Copy the code

EventName is the event type, as an attribute of event_list, and all listeners of eventName type are pushed into the eventName array.

event.prototype.on = function (eventName, content) {... var _event, ctx; _event =this.event_list;
    // Check if event_list exists again and reassign if it does not
    if(! _event) { _event =this.event_list = {};
    } else {
      // Get the listener for the current eventName
      ctx = this.event_list[eventName];
    }
    // Check whether the listener type exists
    // If it does not exist, this event is being listened for the first time
    // Assign the callback function content directly
    if(! ctx) { ctx =this.event_list[eventName] = content;
      // Change the number of subscribers
      ctx.ListenerCount = 1;
    } else if (isFunction(ctx)) {
      // Determine if this attribute is a function (if it is a function, it already has one subscriber)
      // Convert this eventName type from a function to an array
      ctx = this.event_list[eventName] = [ctx, content];
      // The number of subscribers changes to the array length
      ctx.ListenerCount = ctx.length;
    } else if (isArray(ctx)) {
      // Check whether it is an array. If it is an array, push itctx.push(content); ctx.ListenerCount = ctx.length; }... };Copy the code
Once method implementation
event.prototype.once = function () {};
Copy the code

The once method executes only once for subscribed events and immediately deletes the subscription callback function from the corresponding subscription type attribute in event_list. The stored procedure is almost identical to that of the ON method, which also requires a topic of the subscription type and a callback content in response to the event

event.prototype.once = function (eventName, content) {};
Copy the code

The execution of the registered immediately after the event callback to cancel this subscription, but if the same type of event registration multiple callback monitoring, we can’t accurately to delete the current registered once method to monitor the callback, so we usually use queue traversal event listeners, find the corresponding listen callback and then delete them doesn’t work. Fortunately, the great javascript language provides us with a powerful closure feature that decorates the Content in a way that wraps it into an entirely new function.

events.prototype.once = function (event, content) {...// Once and on have the same stored event callback mechanism
    // the dealOnce function wraps the function
    this.on(event, dealOnce(this, event, content)); . }// Wrap the function
function dealOnce(target, type, content) {
    var flag = false;
    // Through the closure feature (which stores external function references in scope)
    function packageFun() {
      // When this listener callback is invoked, the callback method is deleted first
      this.removeListener(type, packageFun);
      if(! flag) { flag =true;
        // Because of the closure, the original listener callback will remain, so it will still be executed
        content.apply(target, arguments);
      }
      packageFun.content = content;
    }
    return packageFun;
  }
Copy the code

The implementation of once actually rewraps the callback we passed ourselves and binds it to the wrapped function. The wrapped function removeListener() first removes the binding between the callback and the event, and then executes the callback

Emit method implementation
event.prototype.emit = function () {};
Copy the code

The EMIT method is used to publish events, and the driver executes the corresponding listener callback in the event listener queue, so we need a topic of event type

event.prototype.emit = function (eventName[,message][,message1][,...]) {};
Copy the code

Of course, a publish event can also pass an unlimited number of parameters to the listener, which will be passed in turn to all listener callbacks

event.prototype.emit = function (eventName[,message]) {
    var _event, ctx;
    // Except for the first parameter eventNmae, the other parameters are stored in an array
    var args = Array.prototype.slice.call(arguments.1);
    _event = this.event_list;
    // Check whether the stored event queue exists
    if (_event) {
      // If so, get this listener type
      ctx = this.event_list[eventName];
    }
    // Detect the event queue for this listening type
    // Return if it does not exist
    if(! ctx) {return false;
    } else if (isFunction(ctx)) {
      // If it is a yam, execute it directly and pass all arguments to this function (callback function)
      ctx.apply(this, args);
    } else if (isArray(ctx)) {
      // is an array traversal call
      for (var i = 0; i < ctx.length; i++) {
        ctx[i].apply(this, args); }}};Copy the code

Emit should be easier to understand, just find the appropriate type of listener event queue from the object where the event is stored and execute each callback in the queue

RemoveListener method implementation
event.prototype.removeListener = function () {};
Copy the code

Delete a listener callback of a listener type. Obviously, we still need an event type and a listener callback that is removed when the callback in the event pair column is the same as that callback

event.prototype.removeListener = function (eventName, content) {};
Copy the code

Note that if we do have a callback to remove a listening event, we must not use anonymous functions as callbacks in the ON method. This will cause removeListener to fail because anonymous functions are not equal in javascript.

// Remove it if needed

/ / error
event.on('eatting'.function (msg) {});/ / right
event.on('eatting', cb);
/ / callback
function cb (msg) {... }Copy the code
event.prototype.removeListener = function (eventName, content) {
    var _event, ctx, index = 0;
    _event = this.event_list;
    if(! _event) {return this;
    } else {
      ctx = this.event_list[eventName];
    }
    if(! ctx) {return this;
    }
    // If the function is directly delete
    if (isFunction(ctx)) {
      if (ctx === content) {
        delete_event[eventName]; }}else if (isArray(ctx)) {
      // Array traversal
      for (var i = 0; i < ctx.length; i++) {
        if (ctx[i] === content) {
          // listen back to phase etc
          // Starting with the index of the listening callback, subsequent callbacks overwrite previous callbacks
          // Delete the last callback
          // This is equivalent to directly deleting the listener callback that meets the condition
          this.event_list[eventName].splice(i - index, 1);
          ctx.ListenerCount = ctx.length;
          if (this.event_list[eventName].length === 0) {
            delete this.event_list[eventName] } index++; }}}};Copy the code
RemoveAllListener method implementation
event.prototype.removeAllListener = function () {};
Copy the code

EventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName: eventName

event.prototype.removeAllListener = function ([,eventName]) {
    var _event, ctx;
    _event = this.event_list;
    if(! _event) {return this;
    }
    ctx = this.event_list[eventName];
    // Check whether there are arguments
    if (arguments.length === 0&& (! eventName)) {/ / no parameters
      // Convert the key to an array and iterate over it
      // Delete all type listeners in turn
      var keys = Object.keys(this.event_list);
      for (var i = 0, key; i < keys.length; i++) {
        key = keys[i];
        delete this.event_list[key]; }}// Remove parameters directly
    if (ctx || isFunction(ctx) || isArray(ctx)) {
      delete this.event_list[eventName];
    } else {
      return this; }};Copy the code

The main implementation idea is basically the same as above, there is something missing, oh, is the handling of whether the maximum number of ships is exceeded in the ON method

.// Checks if the callback queue has the maxed attribute and if it is false
if(! ctx.maxed) {// Comparisons will only be made if there is an array
      if (isArray(ctx)) {
        var len = ctx.length;
        if (len > (this.MaxEventListNum ? this.MaxEventListNum : this.defaultMaxEventListNum)) { 
        // When the maximum limit is exceeded, an exception warning is issued
          ctx.maxed = true;
          console.warn('events.MaxEventListNum || [ MaxEventListNum ] :The number of subscriptions exceeds the maximum, and if you do not set it, the default value is 10');
        } else {
          ctx.maxed = false; }}}...Copy the code

Events-manage can also be used globally in Vue

events.prototype.install = function (Vue, Option) {
    Vue.prototype.$ev = this;
  }
Copy the code

There is no need to explain more, I think the viewer should understand how to use it (in Vue)

For more specific and detailed usage documentation of this library,Just go right here.

Code word is not easy ah, if you think there is some help, but also please give a big praise 👍 ha ha

(… It’s early morning…)