I was exposed to the publish-subscribe model in my recent work study. The idea of programming in the application is also very extensive, for example in Vue also a large number of use of the design pattern, so will be combined with Vue source code and you talk about their shallow understanding.
What is the main content of the publish subscribe model?
- Publish a function that executes the corresponding callback when it publishes
- Subscribing functions, adding subscribers, passing in functions to be executed when publishing, possibly with additional arguments
- A list of cached subscribers and their callback functions
- Unsubscribe (need to discuss case by case)
In this way, just like the event model in JavaScript, we bind the event function to the DOM node, and when triggered, we apply the publish-subscribe mode.
Let’s implement one of the aboveObserver
The objects are as follows:
// The key-value pair used to store the event name of the subscription and the list of callback functionsfunction Observer() {this.cache = {}} //key: identifies the type of the subscription message. Observer.prototype.on =function (key,fn) {
if(! This.cache [key]){this.cache[key]=[]} this.cache[key].push(fn)} //argumentsfunction (key) {
if(this.cache[key]&&this.cache[key].length>0){
var fns = this.cache[key]
}
for(leti=0; i<fns.length; I++) {Array. The prototype. The shift. The call (the arguments) FNS [I] apply (this, the arguments)}} / / remove the need to pay attention to, if you directly into an anonymous function fn, When you remove is unable to find the function and remove it, flexible way is introduced to a / / pointer to the function, it was this pointer which feeds into the Observer. The prototype. Remove =function (key,fn) {
let fns = this.cache[key]
if(! fns||fns.length===0){return} // If fn is not passed in, then all subscriptions for the event are unsubscribedif(! fn){ fns=[] }else {
fns.forEach((item,index)=>{
if(item===fn){
fns.splice(index,1)
}
})
}
}
//example
var obj = new Observer()
obj.on('hello'.function (a,b) {
console.log(a,b)
})
obj.emit('hello'// The callback to the unsubscribe event must be the named function obj.on('test',fn1 =function () {
console.log('fn1')
})
obj.on('test',fn2 = function () {
console.log('fn2')
})
obj.remove('test',fn1)
obj.emit('test')
Copy the code
Why publish and subscribe? Its advantages are:
- Achieve temporal decoupling (asynchronous communication between components, modules)
- Decoupling between objects, where publish-subscribed objects manage the coupling between objects.
The publish-subscribe model is inVue
The application of
Vue
Application of instance methods in :(Current version :2.5.16)
- Document portal
- Source code portal
- (Introduced flow.js for static type checking)
// vm.$on
export functionEventsMixin (Vue: Class<Component>) {const hookRE = /^hook:/ // / Var. Prototype$on = function(event: string | Array < string >, fn: Function) : Component {const vm: Component = this / / incoming type into an Arrayif (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$on(event[I], fn) // Recurse and pass the appropriate callback}}else{ // (vm._events[event] || (vm._events[event] = [])).push(fn) // optimize hook:event cost by using a boolean flag marked at registration // instead of ahash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true}}return vm
}
// vm.$emit
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
if(process.env.NODE_ENV ! = ='production') {
const lowerCaseEvent = event.toLowerCase()
if(lowerCaseEvent ! == event && vm._events[lowerCaseEvent]) { tip( `Event"${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(vm)} but the handler is registered for "${event}". ` +
`Note that HTML attributes are case-insensitive and you cannot use ` +
`v-on to listen to camelCase events when using in-DOM templates. ` +
`You should probably use "${hyphenate(event)}" instead of "${event}". `)}}let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
for (leti = 0, l = cbs.length; i < l; I ++) {try {CBS [I]. Apply (vm, args)// execute the previously passed callback} catch (e) {handleError(e, vm, 'event handlerfor "${event}"`)}}}return vm
}
Copy the code
Vue also implements vm.$once (listen once); And vm.$off (unsubscribe), you can see how this is done in the same file.
Vue
Application of data update mechanism
- Observer A property of each object added to the subscriber container Dependency(Dep) to notice when data changes.
- Watcher: Listener/subscriber to a property data that notifies the directive to recompile the template and render the UI if the data changes
- Source: source portal – Observer
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
/**
* Walk through each property and convert them into
* getter/setters. This method should only be called when
* value type*/ / Walk (obj: Object) {const keys = object.keys (obj)for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
Copy the code
- Dep object: The subscriber container that maintains the Watcher source portal
exportdefault class Dep { static target: ? Watcher; id: number; subs: Array<Watcher>;constructor() {this.id = uid++ this.subs = [] // store subscribers} // add watcher addSub (sub: Watcher) {this.subs.push(sub)} // Remove removeSub (sub) {remove(this.subs, sub)}depend () {
if(dep.target) {dep.target.adddep (this)}} // Change notificationnotify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
Copy the code
Examples of small applications at work
- Scenario: Wepy-based applets. Because the project itself is not complex enough to use the supplied
redux
However, there are asynchronous operations associated between different components (not just parent and child components). So an Observer object that was implemented at the beginning of this article is mounted on the Wepy object. Bus mechanism as part of communication between components:
wepy.$bus= new Observer() // You can then subscribe and publish messages in different modules and componentsCopy the code
Points to pay attention to
Of course, there are downsides to the publish-subscribe model.
- Creating a subscriber itself consumes memory. After subscribing to a message, perhaps, there will never be a publication, and the subscriber will always be in memory.
- While objects are decoupled, their relationships can be buried deep behind the code, which incurs maintenance costs.
Of course, design patterns exist to help us solve scenario-specific problems, and learning to use them in the right scenario is the most important.
Spread the word
This article is published in Front of Mint Weekly. Welcome to Watch & Star.