preface

The publish-subscribe model is a one-to-many dependency between objects. When the state of an object changes, all dependent objects are notified of the state change.

  • Subscriber registers the events he/she wants to Subscribe to the Event Channel.
  • When the Publisher publishes the Event to the dispatch center, that is, when the Event is triggered, it is up to the dispatch Center to register the processing code for the Fire Event subscribers to the dispatch center.

◾ example

For example, we like to see a public number of articles, but do not know when to release a new article, or to regularly read; At this time, we can pay attention to the public number, when there is an article push, there will be a timely message to inform us of the update of the article.

The above seemingly simple operation, in fact, is a typical release and subscription model, the public number belongs to the publisher, the user belongs to the subscriber; The user will register the event of subscribes to the public account with the dispatch center. The public account acts as the publisher. When a new article is published, the public account will publish the event to the dispatch center, and the dispatch center will send messages to inform the user in time.

◾ publish/subscribe has the advantage of decoupling between objects. In asynchronous programming, more loosely coupled code can be written. The downside is that creating a subscriber itself takes some time and memory, and while it can weaken the connection between objects, it is difficult to keep track of multiple publishers and subscribers nested together.

Handwriting implements the publish-subscriber model

The overall publish-subscriber model is implemented as follows:

  • Create a class class
  • Create a cache list in this class (Dispatch center)
  • onMethod – used to put functionsfnAdd to the cache list (Subscribers register events with the dispatch center)
  • emitMethod – get toeventEvent type, according toeventValue to execute functions in the corresponding cache list (Publishers publish events to the dispatch center, which processes the code)
  • offMethod – can be based oneventEvent type Unsubscribe (unsubscribe)

Next, we follow the above ideas and start the hand-written publish-subscriber model 👇

1. Create an Observer class

Let’s create an Ovserver class:

+ class Observer {+ +}Copy the code

In the Observer class, we need to add a constructor:

class Observer {+constructor(){+ +}}Copy the code

2. Add three core methods

We also need to add three more methods, namely the on, emit and off methods we mentioned earlier. To make this method look more like Vue, we add $to each of them, namely:

  • Adds content to the message queue$on
  • Delete the contents of the message queue$off
  • Triggers the contents of the message queue$emit
class Observer {
    constructor(){} +// Add content '$on' to the message queue
+   $on(){}
+   // Delete the contents of the message queue '$off'
+   $off(){}
+   // Trigger the contents of the message queue '$emit'
+   $emit(){}
}
Copy the code

Let’s leave the details of the method behind and create a subscriber (publisher),

Create an instance using the constructor:

class Observer {
    constructor(){}// Add content '$on' to the message queue
    $on(){}
    // Delete the contents of the message queue '$off'
    $off(){}
    // Trigger the contents of the message queue '$emit'
    $emit(){}
}

+ // Use the constructor to create an instance
+ const person1 = new Observer()
Copy the code

Next, we delegate something to person1, that is, call person1’s $ON method:

class Observer {
    constructor(){}// Add content '$on' to the message queue
    $on() {}
    // Delete the contents of the message queue '$off'
    $off() {}
    // Trigger the contents of the message queue '$emit'
    $emit() {}
}

// Use the constructor to create an instance
const person1 = new Observer();

+ // Delegate something to this' person1 ', calling the '$ON' method of 'person1'
+ person1.$on()
Copy the code

Since you’re delegating something, you need the event name, and you need a callback function when the event fires

  • The event name
  • The callback function

For example, let’s write several events, such as:

class Observer {
    constructor(){}// Add content '$on' to the message queue
    $on() {}
    // Delete the contents of the message queue '$off'
    $off() {}
    // Trigger the contents of the message queue '$emit'
    $emit() {}
}

// Use the constructor to create an instance
const person1 = new Observer();

// Delegate something to this' person1 ', calling the '$ON' method of 'person1'
person1.$on()

+ function handlerA() {+console.log('handlerA'); + + +}function handlerB() {+console.log('handlerB'); + + +}function handlerC() {+console.log('handlerC');
}
Copy the code

We now ask person1 to listen to buy the ruby, and when the ruby arrives, execute the handlerA and handlerB callbacks as follows:

class Observer {
    constructor(){}// Add content '$on' to the message queue
    $on() {}
    // Delete the contents of the message queue '$off'
    $off() {}
    // Trigger the contents of the message queue '$emit'
    $emit() {}
}

// Use the constructor to create an instance
const person1 = new Observer();

// Delegate something to this' person1 ', calling the '$ON' method of 'person1'
+ person1.$on('Buy a ruby', handlerA)
+ person1.$on('Buy a ruby', handlerB)

function handlerA() {
    console.log('handlerA');
}

function handlerB() {
    console.log('handlerB');
}

function handlerC() {
    console.log('handlerC');
}
Copy the code

Person1 is also requested to listen to buy the milk tea, and when the tea arrives, execute the callback handlerC:

class Observer {
    constructor(){}// Add content '$on' to the message queue
    $on() {}
    // Delete the contents of the message queue '$off'
    $off() {}
    // Trigger the contents of the message queue '$emit'
    $emit() {}
}

// Use the constructor to create an instance
const person1 = new Observer();

// Delegate something to this' person1 ', calling the '$ON' method of 'person1'
person1.$on('Buy a ruby', handlerA)
person1.$on('Buy a ruby', handlerB)

+ person1.$on('Buy milk tea', handlerC)


function handlerA() {
    console.log('handlerA');
}

function handlerB() {
    console.log('handlerB');
}

function handlerC() {
    console.log('handlerC');
}
Copy the code

3. Set the cache list

This is where we need the list of caches (message queues) that we talked about earlier, the dispatch center.

Add a cache list to the Observer class:

class Observer {
    constructor(){+this.message = {} // Message queue
    }
    // Add content '$on' to the message queue
    $on() {}
    // Delete the contents of the message queue '$off'
    $off() {}
    // Trigger the contents of the message queue '$emit'
    $emit() {}
}

// Use the constructor to create an instance
const person1 = new Observer();
Copy the code

The cache list message object does the following:

Delegate a buy type to Person1, and execute the callback functions handlerA and handlerB when it’s done

class Observer {
    constructor() {
        this.message = {} // Message queue
    }
    // Add content '$on' to the message queue
    $on() {}
    // Delete the contents of the message queue '$off'
    $off() {}
    // Trigger the contents of the message queue '$emit'
    $emit() {}
}

// Use the constructor to create an instance
const person1 = new Observer();

+ person1.$on('buy',handlerA);
+ person1.$on('buy',handlerB);

function handlerA() {
    console.log('handlerA');
}

function handlerB() {
    console.log('handlerB');
}

function handlerC() {
    console.log('handlerC');
}
Copy the code

We want to add the above content to the message queue with $on, which is equivalent to adding a buy property to the message object with a value of [handlerA, handlerB], which does the following:

class Observer {
    constructor() {
        this.message = {
+           buy: [handlerA, handlerB]
        }
    }
    // Add content '$on' to the message queue
    $on() {}
    // Delete the contents of the message queue '$off'
    $off() {}
    // Trigger the contents of the message queue '$emit'
    $emit() {}
}
Copy the code

Once the requirements are clear, start writing the $on function 👇👇👇

Implement the $on method

Review a line of code:

person1.$on('buy',handlerA);
Copy the code

Obviously we pass two arguments to the $on method:

  • type: Event name (event type)
  • callback: callback function
class Observer {
    constructor() {
        this.message = {} // Message queue
    }

+   /** + * '$on' adds content to the message queue + *@param {*} Type Event name (event type) + *@param {*} Callback function + */
+   $on(type, callback) {}
    // Delete the contents of the message queue '$off'
    $off() {}
    // Trigger the contents of the message queue '$emit'
    $emit() {}
}

// Use the constructor to create an instance
const person1 = new Observer();

person1.$on('buy', handlerA);
person1.$on('buy', handlerB);
Copy the code

Let’s take a look at how to add content to a message queue, which is an object that can add event content as follows:

class Observer {
    constructor() {
        this.message = {} // Message queue
    }

    /** * '$on' adds content to the message queue *@param {*} Type Event name (event type) *@param {*} Callback function */
    $on(type, callback) {
+       this.message[type] = callback;
    }
    // Delete the contents of the message queue '$off'
    $off() {}
    // Trigger the contents of the message queue '$emit'
    $emit() {}
}
Copy the code

But we know that each attribute value in the message queue is an array:

this.message = {
    buy: [handlerA, handlerB]
}
Copy the code

That is, there are multiple messages (callback functions) for each event type. In this case, we create an array for each event type.

  1. First check if there is this property (event type)
  2. If it does not, an empty array is initialized
  3. If it has this attribute, push a new callback after it

The code implementation is as follows:

class Observer {
    constructor() {
        this.message = {} // Message queue
    }

    /** * '$on' adds content to the message queue *@param {*} Type Event name (event type) *@param {*} Callback function */
    $on(type, callback) {
+       // Check if there is such an attribute (event type)
+       if (!this.message[type]) {
+           // If there is no such property, an empty array is initialized
+           this.message[type] = []; +},// Push a new callback after it if it has this attribute
+       this.message[type].push(callback)
    }
    // Delete the contents of the message queue '$off'
    $off() {}
    // Trigger the contents of the message queue '$emit'
    $emit() {}
}
Copy the code

The code implementation for $on is shown above. Let’s add the use case and introduce it into an HTML file to test it:

Observe.js

class Observer {
    constructor() {
        this.message = {} // Message queue
    }

    /** * '$on' adds content to the message queue *@param {*} Type Event name (event type) *@param {*} Callback function */
    $on(type, callback) {
        // Check if there is such an attribute (event type)
        if (!this.message[type]) {
            // If there is no such property, an empty array is initialized
            this.message[type] = [];
        }
        // Push a new callback after it if it has this attribute
        this.message[type].push(callback)
    }
    // Delete the contents of the message queue '$off'
    $off() {}
    // Trigger the contents of the message queue '$emit'
    $emit() {}
}

function handlerA() {
    console.log('handlerA');
}
function handlerB() {
    console.log('handlerB');
}
function handlerC() {
    console.log('handlerC');
}

// Use the constructor to create an instance
const person1 = new Observer();

person1.$on('buy', handlerA);
person1.$on('buy', handlerB);

console.log('person1 :>> ', person1);
Copy the code

Oberver.html

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <title>Document</title>
</head>
<body>
    <! Observe.js -->
    <script src=".. /JS/Observer.js"></script>
</body>
</html>
Copy the code

Output result:

The printed person1 is of type Oberver and contains a message, which is the message queue we defined. The buy event has two messages: [handlerA, handlerB]. The test passed 👏👏👏

Next, we implement the $off method

Implement the $off method

The $off method is used to delete the contents of the message queue

The $off method can be written in two ways:

  1. person1.$off("buy")– Delete the entirebuyThe event type
  2. person1.$off("buy",handlerA)– just deletehandlerAMessage, reservedbuyOther messages in the event list

Like the $on method, the $off method requires both type and callback methods:

class Observer {
    constructor() {
        this.message = {} // Message queue
    }

    /** * '$on' adds content to the message queue *@param {*} Type Event name (event type) *@param {*} Callback function */
    $on(type, callback) {
        // Check if there is such an attribute (event type)
        if (!this.message[type]) {
            // If there is no such property, an empty array is initialized
            this.message[type] = [];
        }
        // Push a new callback after it if it has this attribute
        this.message[type].push(callback)
    }

+   /** + * $off Deletes the contents of the message queue + *@param {*} Type Event name (event type) + *@param {*} Callback function + */
+   $off(type, callback) {}
    
    // Trigger the contents of the message queue '$emit'
    $emit() {}
}
Copy the code

The $off method is implemented as follows:

  • Check if there is a subscription, that is, if there is an event of type type in the message queue, return if there is no subscription
  • Check whether fn is present
    • Delete the entire event without fn
    • If there is fn, just delete the message fn

The code implementation is as follows:

class Observer {
    constructor() {
        this.message = {} // Message queue
    }

    /** * '$on' adds content to the message queue *@param {*} Type Event name (event type) *@param {*} Callback function */
    $on(type, callback) {
        // Check if there is such an attribute (event type)
        if (!this.message[type]) {
            // If there is no such property, an empty array is initialized
            this.message[type] = [];
        }
        // Push a new callback after it if it has this attribute
        this.message[type].push(callback);
    }

    /** * $off Removes the contents of the message queue *@param {*} Type Event name (event type) *@param {*} Callback function */
    $off(type, callback) {
+       // Check if there is a subscription, that is, if there is an event of type type in the message queue, return if there is no subscription
+       if (!this.message[type]) return;
+       // Check whether the argument callback exists
+       if(! callback) { +// If no callback, delete the entire event ß
+           this.message[type] = undefined; +},// If there is a callback, just delete the message callback (filter the message method)
+       this.message[type] = this.message[type].filter((item) = >item ! == callback); }// Trigger the contents of the message queue '$emit'
    $emit() {}
}
Copy the code

$off = $off = $off

class Observer {... }function handlerA() {
    console.log('handlerA');
}
function handlerB() {
    console.log('handlerB');
}
function handlerC() {
    console.log('handlerC');
}

// Use the constructor to create an instance
const person1 = new Observer();

person1.$on('buy', handlerA);
person1.$on('buy', handlerB);
person1.$on('buy', handlerC);

console.log('person1 :>> ', person1);
Copy the code

Output result:

● To test deleting a single message, use $off to delete the handlerC message

class Observer {... }function handlerA() {
    console.log('handlerA');
}
function handlerB() {
    console.log('handlerB');
}
function handlerC() {
    console.log('handlerC');
}

// Use the constructor to create an instance
const person1 = new Observer();

person1.$on('buy', handlerA);
person1.$on('buy', handlerB);
person1.$on('buy', handlerC);

console.log('person1 :>> ', person1);

+ // Delete the handlerC message
+ person1.$off('buy',handlerC);

+ console.log('person1 :>> ', person1);
Copy the code

Output result:

The test passed 🥳🥳🥳

● To test deleting the entire event type, use $off to delete the entire Buy event

class Observer {... }function handlerA() {
    console.log('handlerA');
}
function handlerB() {
    console.log('handlerB');
}
function handlerC() {
    console.log('handlerC');
}

// Use the constructor to create an instance
const person1 = new Observer();

person1.$on('buy', handlerA);
person1.$on('buy', handlerB);
person1.$on('buy', handlerC);

console.log('person1 :>> ', person1);

// Delete the handlerC message
person1.$off('buy',handlerC);

console.log('person1 :>> ', person1);

+ // Delete the buy event
+ person1.$off('buy');

+ console.log('person1 :>> ', person1);
Copy the code

Output result:

Perfect!!!!!! Test pass ✅

Thus, the two functions of $OFF have been successfully implemented 👏👏👏

● There is a small detail about the implementation of $off 👇

Javascript can remove an object property in two ways:

  1. The delete operator
  2. obj.key = undefined;(equivalent toobj[key] = undefined;)

The differences between the two methods:

1. The delete operator removes specified attributes from an object, but it does much more work than its “alternative” setting, object[key] = undefined.

And this method has many limitations. For example, the following situations need to be considered:

  • If the property you are trying to delete does not exist, delete will have no effect, but will still return true

  • If an object has an attribute in its stereotype chain with the same name as the attribute to be deleted, then the object uses that attribute in the stereotype chain after deleting the attribute (that is, delete only works on its own attribute).

  • Any properties declared using var cannot be removed from the global scope or function scope.

    • In this case, the delete operation cannot delete any function in the global scope (whether the function comes from a function declaration or function expression).
    • Except for functions in the global scope that cannot be deleted, functions in an object can be deleted by the delete operation.
  • Any property declared with a let or const cannot be removed from the scope in which it was declared.

  • The non-configurable property of the system cannot be removed. This means that properties of built-in objects like Math, Array, and Object, as well as properties made unsettable using the object.defineProperty () method, cannot be removed.

2. obj[key] = undefined; This choice is not the right answer to the question, because you just replace an attribute with undefined, and the attribute itself is still there. However, if you use it carefully, you can greatly speed up some algorithms.

All right, back to business 🙌

Let’s start implementing the third method, $emit 🧗♀️

6. Implementation$emitmethods

$emit is used to trigger the contents of the message queue:

  • The method needs to pass in a type parameter that determines which event to fire;

  • The main process is to poll the type event (for loop), executing the callback function 👌 for each message.

The specific code is as follows:

class Observer {
    constructor() {
        this.message = {} // Message queue
    }

    /** * '$on' adds content to the message queue *@param {*} Type Event name (event type) *@param {*} Callback function */
    $on(type, callback) {
        // Check if there is such an attribute (event type)
        if (!this.message[type]) {
            // If there is no such property, an empty array is initialized
            this.message[type] = [];
        }
        // Push a new callback after it if it has this attribute
        this.message[type].push(callback);
    }

    /** * $off Removes the contents of the message queue *@param {*} Type Event name (event type) *@param {*} Callback function */
    $off(type, callback) {
        // Check if there is a subscription, that is, if there is an event of type type in the message queue, return if there is no subscription
        if (!this.message[type]) return;
        // Check whether the argument callback exists
        if(! callback) {// If there is no callback, delete the entire event
            this.message[type] = undefined;
            return;
        }
        // If there is a callback, just delete the message callback (filter the message method)
        this.message[type] = this.message[type].filter((item) = >item ! == callback); } +/** + * $emit triggers the contents of the message queue + *@param {*} Type Event name (event type) + */
+   $emit(type) {
+       // Check if there is a subscription
+       if(!this.message[type]) return;
+       // If there is a subscription, poll the 'type' event (for loop)
+       this.message[type].forEach(item= >{+// Execute the callback function for each message one by one+ item() + }); +}}Copy the code

Call it a day 🏌️♀️

Put it to the test

class Observer {... }function handlerA() {
    console.log('buy handlerA');
}
function handlerB() {
    console.log('buy handlerB');
}
function handlerC() {
    console.log('buy handlerC');
}

// Use the constructor to create an instance
const person1 = new Observer();

+ person1.$on('buy', handlerA);
+ person1.$on('buy', handlerB);
+ person1.$on('buy', handlerC);

console.log('person1 :>> ', person1);

+ // Trigger the buy event
+ person1.$emit('buy')
Copy the code

Output result:

The test passed 👏👏👏

The complete code

This article implements the simplest published subscriber model, with only four core elements:

  1. Cache listmessage
  2. Adds content to the message queue$on
  3. Delete the contents of the message queue$off
  4. Triggers the contents of the message queue$emit

Publish subscriber pattern complete code implementation:

The full version of the code is longer, here if it is not convenient to go to my GitHub to see, I specifically maintain a front-end BLOG warehouse: github.com/yuanyuanbyt…

class Observer {
    constructor() {
        this.message = {} // Message queue
    }

    /** * '$on' adds content to the message queue *@param {*} Type Event name (event type) *@param {*} Callback function */
    $on(type, callback) {
        // Check if there is such an attribute (event type)
        if (!this.message[type]) {
            // If there is no such property, an empty array is initialized
            this.message[type] = [];
        }
        // Push a new callback after it if it has this attribute
        this.message[type].push(callback);
    }

    /** * $off Removes the contents of the message queue *@param {*} Type Event name (event type) *@param {*} Callback function */
    $off(type, callback) {
        // Check if there is a subscription, that is, if there is an event of type type in the message queue, return if there is no subscription
        if (!this.message[type]) return;
        // Check whether the argument callback exists
        if(! callback) {// If there is no callback, delete the entire event
            this.message[type] = undefined;
            return;
        }
        // If there is a callback, just delete the message callback (filter the message method)
        this.message[type] = this.message[type].filter((item) = >item ! == callback); }/** * $emit triggers the contents of the message queue *@param {*} Type Event name (event type) */
    $emit(type) {
        // Check if there is a subscription
        if(!this.message[type]) return;
        // If there is a subscription, poll the 'type' event (for loop)
        this.message[type].forEach(item= > {
            // Execute the callback function for each message one by oneitem() }); }}Copy the code

❤ ️ end

If this article is helpful to your study, please like it 👍, collect it ⭐ and leave a comment 📝. Your support is my motivation to create and share!

If you have any questions during the learning process,Click here to, you can get my contact information and communicate with me ~

Pay attention to the public account “front-end yuan Yuan”, the first time to get updates.

More full more detailed quality content, click here to view