Different language – same pattern
Recently in the knowledge of the design pattern, and in the work, do some of the requirements, just directly use the publication and subscription model to achieve, which makes me more interested in the publication and subscription design pattern, so take this opportunity to talk to you about this good east!
In fact, in the early days of JQ development, there were many places where we could publish and subscribe, such as trigger and on methods
Now in VUE, the emit and on methods. They all seem to come with publish-subscribe attributes that make development more efficient and easy to use
So without further ado, let’s take a look at the publish-subscribe model
Publish and subscribe model
When it comes to publish-subscribe, it’s a one-to-many dependency between objects (not variety show versus 100), where when an object’s state is sent, all dependent objects are notified of the change
Just because you don’t have a lot of words doesn’t mean it doesn’t work, so let’s go on and see what it does
role
- Widely used in asynchronous programming (instead of passing callback functions)
- Write code for loose coupling between objects
Is that all? Yeah, not too many dots, just enough. We all know the famous proverb that Rome was not built in a day
Of course, fat people aren’t made in a day. So if we want to realize a publish and subscribe mode of our own, we need to use it in our work in the future, and we need to start with the simplest one
Custom events
letcorp = {}; Corp.list = []; corp.list = []; // Subscribe to event corp.on =functionThis.list.push (fn) {this.list.push(fn); }; // Publish event corp.emit =function() {this.list.forEach(cb => {cb.apply(this, arguments); }); }; // Test case corp.on(function (position, salary) {
console.log('Your position is:' + position);
console.log(Expected salary: + salary);
});
corp.on(function(skill, hobby) {
console.log('Your skills are:' + skill);
console.log('Hobbies:' + hobby);
});
corp.emit('front end', 10000);
corp.emit('Serve tea and pour water'.'football'); /* Your position is: front-end Expected Salary: 10000 Your skills are: front-end hobby: 10000 Your position is: serving tea and pouring water Expected Salary: Football Your skills are: serving tea and pouring water hobby: football */Copy the code
A simple publish-subscribe model is implemented with custom events, but the printed results are a little embarrassing. According to?
Because under normal circumstances, you want to print something like this:
/* Your position is: front-end Expected Salary: 10000 Your skills are: serving tea and pouring water hobby: football */Copy the code
And the reason why this happens is that in the on method all the fn functions are in the list at once. However, all that is needed is a simple key value. Let’s rewrite the above code
letcorp = {}; Corp.list = {}; corp.on =function(key, fn) {// If there is no corresponding key in the object // this means that there is no subscription // then create a cache list for the keyif(! this.list[key]) { this.list[key] = []; } this.list[key].push(fn); }; corp.emit =function() {// The first argument is the corresponding key value // directly use the arrayshiftMethods take outletkey = [].shift.call(arguments), fns = this.list[key]; // Return if there is no function in the cache listfalse
if(! fns || fns.length === 0) {return false; ForEach (fn => {fn.apply(this, arguments); }); }; // Test case corp.on('join', (position, salary) => {
console.log('Your position is:' + position);
console.log(Expected salary: + salary);
});
corp.on('other', (skill, hobby) => {
console.log('Your skills are:' + skill);
console.log('Hobbies:' + hobby);
});
corp.emit('join'.'front end', 10000);
corp.emit('join'.'back-end', 10000);
corp.emit('other'.'Serve tea and pour water'.'football'); /* Your position is: Front-end expected Salary: 10000 Your position is: back-end expected Salary: 10000 Your skills are: serving tea and pouring water hobby: football */Copy the code
Give me a generic one
Now let’s do a generic publish-subscribe implementation, just like we did before, but this time we’re going to call it a little bit more ceremonious. let’s just call it event and look at the code
let event = {
list: {},
on(key, fn) {
if(! this.list[key]) { this.list[key] = []; } this.list[key].push(fn); },emit() {
let key = [].shift.call(arguments),
fns = this.list[key];
if(! fns || fns.length === 0) {return false; } fns.forEach(fn => { fn.apply(this, arguments); }); }, remove(key, fn) {// This time we added unsubscribe methodletfns = this.list[key]; // If there is no function in the cache list, returnfalse
if(! fns)return false; // If no function is passed // all functions in the cache list corresponding to the key value will be clearedif(! fn) { fns && (fns.length = 0); }elseForEach ((cb, I) => {forEach(cb, I) => {if(cb === fn) { fns.splice(i, 1); }}); }}};function cat() {
console.log('Meow, meow, meow.');
}
function dog() {
console.log('Let's hope hope hope');
}
event.on('pet', data => {
console.log('Receive data');
console.log(data);
});
event.on('pet', cat);
event.on('pet', dog); // Unsubscribe the dog method event.remove('pet', dog); / / release event. Emit ('pet'['two ha'.Persian cat]); /* Receive data ['two ha'.Persian cat[Together meow meow meow */Copy the code
In fact, this can be used to achieve a publish subscription model, in fact, it is relatively simple, to think of it again and again
Ideas:
- Create an object (cache list)
- The on method is used to add the callback function fn to the cache list
- The emit method takes the first key from arguments and executes the function in the cache list according to the key value
- The remove method can unsubscribe based on the key value
Application at work
Insert ads
To show you one first, in this news transcoding page project, I was responsible for writing the content of the following recommendation stream (even if people like to read there). As shown in the figure below
After all, I can not give him my code, let him take to develop it, it is not enough effort, and to be familiar with the code and start to write the logic of AD insertion, very toss, time should not be wasted
So now we have publish-subscribe, so I don’t have to worry about the logic of AD insertion. I am still me, is not the same color fireworks, ha ha ha, digressive
After communication, I just need to send the user the page number of the page to which he browsed. So I just need to write a sentence in the code I’m developing, using the event implementation above to represent it
/ / to omit...renderEvent.emit () {// I only need to send the key and page number to render.'soAd', page); } // omit...Copy the code
dot
Let’s see one more, my friend. Dotting is mainly used to record user behavior, so in the mobile map search new version of the development of the time will also join dotting code, and then statistics pv, UV, CTR and other data, then directly look at the picture
Very simple, I just add this dot when the request is completed and rendered to the page, look at the simple code (this is not project code, just for example)
// main.js
render() {// omit... // When rendering to the page, send this event // and then listen for event.emit('relatedDD'.'related');
}
// log.js
event.on('relatedDD'.type => {
console.log(type); // 'related'// Monitor.log ({type
}, 'disp');
});
Copy the code
The above code is just a simple lift chestnut, if there is no understanding of the dozen, then I will be a little simple description
Dot commonly used is to send a picture of the request, according to the number of requests to statistics, the middle will be based on different parameters to do statistics.
For example, if you want to know how many users have seen the “guess what you like” content, when filtering data, you will write type as related directly
This is not the end of the story though, as I discovered that one of the core modules in Node (Events) is exactly the publish-subscribe model described above. This is no coincidence, nor is it a drill. Then the heart ripples, dancing. Let’s Go! Let’s Go!
Seriously – This is the core module of Node
For those of you who have used Node, this module is a very important module in Node. After using it, you can find that it is completely capitalized publish and subscribe mode
It is almost ubiquitous, so no more nonsense, the implementation is still. Let’s see how it works, let’s see how it works with a test case, okay
The test case
/ {'love', [findboy, drink]} // The purpose of the listener is to construct a one-to-many relationship between objects. On // will publish the array functions in sequence to emit // [findboy, drink] //let EventEmitter = require('events'); // Use what we wrote nextlet EventEmitter = require('./events');
let util = require('util');
function Girl() {// Girl inherits EventEmitter (Girl, EventEmitter); // equivalent to girl.prototype. __proto__ = EventEmitter. Prototypelet girl = new Girl();
let drink = function (data) {
console.log(data);
console.log('drink');
};
let findboy = function () {
console.log('friends');
};
girl.on('newListener'.function (eventName) {
// console.log('Name:' + eventName);
});
girl.on('marriage'.function() {});
girl.setMaxListeners(3);
console.log(girl.getMaxListeners());
girl.once('love', drink); / / {'love': [drink]}
girl.once('love', drink); / / {'love': [drink]}
girl.prependListener('love'.function () {
console.log('before');
});
girl.once('love', drink); / / {'love': [drink]}
girl.emit('love'.'1');
Copy the code
The above code is how to use the Events core module. Don’t be stingy
Implement an EventEmitter
Now comes the most important and exciting time to start implementing an EventEmitter
function EventEmitter() {// Use object.create (null) instead of an empty Object {} // This._events = object.create (null); } / / the default maximum number of binding EventEmitter defaultMaxListeners = 10; / / the same method on EventEmitter. Prototype. AddListener = EventEmitter. Prototype. On; / / return to monitor the event name EventEmitter. Prototype. EventNames =function () {
returnObject.keys(this._events); }; / / set the maximum number of listening EventEmitter. Prototype. SetMaxListeners =function(n) { this._count = n; }; / / return to monitor several EventEmitter. Prototype. GetMaxListeners =function () {
returnthis._count ? this._count : this.defaultMaxListeners; }; / / to monitor EventEmitter. Prototype. On =function (type, cb, flag) {// The default value, if there is no _events, create one for itif(! this._events) { this._events = Object.create(null); } // Instead of newListener, newListener should do the followingif (type! = ='newListener') {
this._events['newListener'] && this._events['newListener'].forEach(listener => {
listener(type);
});
}
if (this._events[type]) {// Add forward or backward based on the flag passed inif (flag) {
this._events[type].unshift(cb);
} else {
this._events[type].push(cb); }}else {
this._events[type] = [cb]; } // The number of listening events cannot exceed the maximum number of listening eventsif (this._events[type].length === this.getMaxListeners()) {
console.warn('Warning - warning - warning'); }}; / / add forward EventEmitter. Prototype. PrependListener =function (type, cb) {
this.on(type, cb, true);
};
EventEmitter.prototype.prependOnceListener = function (type, cb) {
this.once(type, cb, true); }; / / to monitor an EventEmitter. Prototype. Once =function (type, cb, flag) {// Bind first, call later deletefunction wrap() { cb(... arguments); this.removeListener(type, wrap); } // Wrap. Listen = cb; this.on(type, wrap, flag); }; / / remove to monitor type EventEmitter. Prototype. RemoveListener =function (type, cb) {
if (this._events[type]) {
this._events[type] = this._events[type].filter(listener => {
returncb ! == listener && cb ! == listener.listen; }); }}; EventEmitter.prototype.removeAllListener =function() { this._events = Object.create(null); }; / / return all types of listening EventEmitter. Prototype. Listeners =function (type) {
return this._events[type]; }; / / release EventEmitter. Prototype. Emit =function (type. args) {if (this._events[type]) {
this._events[type].forEach(listener => {
listener.call(this, ...args);
});
}
};
module.exports = EventEmitter;
Copy the code
Above, we have made efforts to realize the core module Events of Node and complete the functions of EventEmitter. Congratulations! Give yourself a compliment!
Finished is finished, but we still have to write repeatedly, after all, no photographic memory ability, or to work hard, come on, come on
Haha, so at the end of the day, just a little summary
conclusion
Advantages:
- Decoupling between objects
- In asynchronous programming, code can be written with looser coupling
Disadvantages:
- Creating a subscriber itself takes time and memory
- When multiple publishers and subscribers are nested together, the application is difficult to keep track of
Strong as publish subscription model, but also strong wine is good, do not drink truth oh. Overuse, will appear the above shortcomings. However reasonable development reasonable utilization, this is not what big problem.
Take advantage of this most common pattern to improve your programming. That’s all for today, thanks for watching. Haha, see you next time! See U Again