introduce
The observer mode is also called publish/Subscribe. It defines a one-to-many relationship, allowing multiple observer objects to listen to a topic object at the same time. When the state of this topic object changes, all observer objects will be notified, so that they can automatically update themselves.
Benefits of using observer mode:
1. Supports simple broadcast communication and automatically notifies all subscribed objects.
2. After the page is loaded, the target object can easily have a dynamic correlation with the observer, which increases flexibility.
3. The abstract coupling relationship between the target object and the observer can be expanded and reused separately.
Text (version 1)
The implementation of the observer pattern in JS is achieved through callbacks. Let’s first define a pubsub object, which contains 3 methods: subscription, unsubscribe, and publish.
The code copy is as follows:
var pubsub = {};
(function (q) {
var topics = {}, // Array stored by the callback function
subUid = -1;
// Publish method
q.publish = function (topic, args) {
if (!topics[topic]) {
return false;
}
setTimeout(function () {
var subscribers = topics[topic],
len = subscribers ? subscribers.length : 0;
while (len--) {
subscribers[len].func(topic, args);
}
}, 0);
return true;
};
//Subscribe method
q.subscribe = function (topic, func) {
if (!topics[topic]) {
topics[topic] = [];
}
var token = (++subUid).toString();
topics[topic].push({
token: token,
func: func
});
return token;
};
//Unsubscription method
q.unsubscribe = function (token) {
for (var m in topics) {
if (topics[m]) {
for (var i = 0, j = topics[m].length; i < j; i++) {
if (topics[m][i].token === token) {
topics[m].splice(i, 1);
return token;
}
}
}
}
return false;
};
} (pubsub));
How to use it is as follows:
The code copy is as follows:
//Come, subscribe to one
pubsub.subscribe('example1', function (topics, data) {
console.log(topics + ": " + data);
});
// Notice of release
pubsub.publish('example1', 'hello world!');
pubsub.publish('example1', ['test', 'a', 'b', 'c']);
pubsub.publish('example1', [{ 'color': 'blue' }, { 'text': 'hello'}]);
How about it? Isn't it very good to use? But there is a problem with this method, that is, there is no way to unsubscribe. If you want to unsubscribe, you must specify the name of the unsubscribe, so let's go to another version:
The code copy is as follows:
// Assign the subscription to a variable to unsubscribe
var testSubscription = pubsub.subscribe('example1', function (topics, data) {
console.log(topics + ": " + data);
});
// Notice of release
pubsub.publish('example1', 'hello world!');
pubsub.publish('example1', ['test', 'a', 'b', 'c']);
pubsub.publish('example1', [{ 'color': 'blue' }, { 'text': 'hello'}]);
//Unsubscription
setTimeout(function () {
pubsub.unsubscribe(testSubscription);
}, 0);
//Publish again to verify whether the information can still be output
pubsub.publish('example1', 'hello again! (this will fail)');
Version 2
We can also use the characteristics of the prototype to implement an observer pattern, the code is as follows:
The code copy is as follows:
function Observer() {
this.fns = [];
}
Observer.prototype = {
subscribe: function (fn) {
this.fns.push(fn);
},
unsubscribe: function (fn) {
this.fns = this.fns.filter(
function (el) {
if (el !== fn) {
return el;
}
}
);
},
update: function (o, thisObj) {
var scope = thisObj || window;
this.fns.forEach(
function (el) {
el.call(scope, o);
}
);
}
};
//test
var o = new Observer;
var f1 = function (data) {
console.log('Robbin: ' + data + ', work quickly!');
};
var f2 = function (data) {
console.log('Randall: ' + data + ', find him to get some extra salary!');
};
o.subscribe(f1);
o.subscribe(f2);
o.update("Tom is back!")
//Unsubscribe to f1
o.unsubscribe(f1);
//Check again
o.update("Tom is back!");
If the filter or forEach function is not found, it may be because your browser is not new enough and does not support new standard functions for the time being. You can define it yourself in the following way:
The code copy is as follows:
if (!Array.prototype.forEach) {
Array.prototype.forEach = function (fn, thisObj) {
var scope = thisObj || window;
for (var i = 0, j = this.length; i < j; ++i) {
fn.call(scope, this[i], i, this);
}
};
}
if (!Array.prototype.filter) {
Array.prototype.filter = function (fn, thisObj) {
var scope = thisObj || window;
var a = [];
for (var i = 0, j = this.length; i < j; ++i) {
if (!fn.call(scope, this[i], i, this)) {
continue;
}
a.push(this[i]);
}
return a;
};
}
Version 3
If you want multiple objects to have the function of observer publishing subscription, we can define a common function and then apply the function of the function to the object that requires observer function. The code is as follows:
The code copy is as follows:
//Universal code
var observer = {
//subscription
addSubscriber: function (callback) {
this.subscribers[this.subscribers.length] = callback;
},
//Unsubscription
removeSubscriber: function (callback) {
for (var i = 0; i < this.subscribers.length; i++) {
if (this.subscribers[i] === callback) {
delete (this.subscribers[i]);
}
}
},
//release
publish: function (what) {
for (var i = 0; i < this.subscribers.length; i++) {
if (typeof this.subscribers[i] === 'function') {
this.subscribers[i](what);
}
}
},
// Make object o have observer function
make: function (o) {
for (var i in this) {
o[i] = this[i];
o.subscribers = [];
}
}
};
Then subscribe to 2 objects blogger and user, use the observer.make method to make these two objects have observer functions, the code is as follows:
The code copy is as follows:
var blogger = {
Recommend: function (id) {
var msg = 'dudu Recommended post:' + id;
this.publish(msg);
}
};
var user = {
vote: function (id) {
var msg = 'Someone voted!ID=' + id;
this.publish(msg);
}
};
observer.make(blogger);
observer.make(user);
The usage method is relatively simple. Subscribe to different callback functions so that you can register with different observer objects (or multiple observer objects can be registered at the same time):
The code copy is as follows:
var tom = {
read: function (what) {
console.log('Tom saw the following message:' + what)
}
};
var mm = {
show: function (what) {
console.log('mm saw the following message:' + what)
}
};
// Subscribe
blogger.addSubscriber(tom.read);
blogger.addSubscriber(mm.show);
blogger.recommend(123); //Call publish
//Unsubscription
blogger.removeSubscriber(mm.show);
blogger.recommend(456); //Call publish
//Subscribe to another object
user.addSubscriber(mm.show);
user.vote(789); //Call publish
jQuery version
According to the on/off function added in jQuery version 1.7, we can also define the observer of jQuery version:
The code copy is as follows:
(function ($) {
var o = $({});
$.subscribe = function () {
o.on.apply(o, arguments);
};
$.unsubscribe = function () {
o.off.apply(o, arguments);
};
$.publish = function () {
o.trigger.apply(o, arguments);
};
} (jQuery));
The calling method is simpler than the above three versions:
The code copy is as follows:
//Callback function
function handle(e, a, b, c) {
// `e` is an event object, no attention is required
console.log(a + b + c);
};
//subscription
$.subscribe("/some/topic", handle);
//release
$.publish("/some/topic", ["a", "b", "c"]); // Output abc
$.unsubscribe("/some/topic", handle); // Unsubscribe
//subscription
$.subscribe("/some/topic", function (e, a, b, c) {
console.log(a + b + c);
});
$.publish("/some/topic", ["a", "b", "c"]); // Output abc
//Unsubscribe (Unsubscribe uses the /some/topic name, not the callback function, which is different from the example of version 1
$.unsubscribe("/some/topic");
It can be seen that its subscription and unsubscribe use string names, not callback function names, so even if the incoming anonymous function is passed, we can unsubscribe.
Summarize
The use of observers is: when an object changes to change other objects at the same time and it does not know how many objects need to be changed, it should consider using the observer mode.
Overall, what the observer pattern does is decouple, making both sides of the coupling rely on abstraction rather than concrete. This makes it possible that the changes of each other will not affect the changes on the other side.