I. Concept of observer model

The observer pattern (also known as the publisk-subscribe pattern) defines a one-to-many dependency between objects, in which all dependent objects are notified when an object’s state changes.

Official definition from JavaScript Design Patterns and Development Practices.

2. In-depth understanding of application scenarios

The observer model (publish-subscribe) is widely used both in the application world and in the real world.

2.1 Observer model in real life

Let’s look at a real example, a case of selling a house to buy a house. The sales office is the publisher, the buyer is the subscriber, listening to subscribe to the building sales news, the middle needs a record of the building roster. After thinking clearly, we can use JavaScript to implement, the specific code is as follows:

Var event = {clientList: [], listen: function(key, fn){if(! this.clientList[key]){ this.clientList[key] = []; } this.clientList[key].push(fn); / / subscribe message added to the cache list}, the trigger: function () {var key = Array. The prototype. The shift. The call (the arguments), FNS = this. ClientList [key]; if(! fns || ! fns.length) { return false; } for(var i = 0, fn; fn = fns[i++];) { fn.apply(this, arguments); }}, function(key, fn){var FNS = this.clientList[key]; if(! fns) return false; // If the message corresponding to the key is not subscribed, return if(! }else{for(var l = fn. Length -1; var l = fn. l>=0; Var _fn = FNS [l]; var _fn = FNS [l]; if(_fn === fn){ fns.splice(l,1); Var installEvent = function(obj){for(var I in event) {obj[I] = event[I]; var installEvent = function(obj){obj[I] = event[I]; }}Copy the code

Above, we have completed the design and implementation of the basic publish and subscribe mode, in which the event object defines the message cache queue clientList, the listener subscription function LISTEN, the publish function trigger, and the unsubscribe function remove.

2.2 Implementation principle of Vue bidirectional binding

The Vue bidirectional binding principle (also known as the responsive principle) is a frequent interview problem, and its internal implementation also belongs to the observer mode. Today, we will analyze it. First, we will look at the flow chart of the reactive principle on the official website:From the figure above, we can see that there are three key players in the Vue bidirectional binding implementation logic:

  • Observer: In the role structure of Vue two-way data binding, the observer is not only a data listener, it also needs to forward the data it listens to — that is, it is also a publisher.
  • Watcher (Subscriber) : The Observer forwards the data to the actual subscriber, the Watcher object. The Watcher subscriber acts as a bridge between Observe and Compile. It does a) add from to the attribute subscriber (DEP) when it instantiates itself; B. Must have an update() method; C, can call its own update() method and trigger the callback bound in Compile when dep.notice() is notified of property changes.
  • Compile: A role unique to the MVVM framework that scans and parses each node element instruction, as well as data initialization and the creation of subscribers

Source code can be found in the concrete implementation: the Observer: the SRC/core/Observer/index. Js

export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor (value: any) { this.value = value this.dep = new Dep() ... DefineReactive walk(obj: Object) {const keys = object.keys (obj) for (let I = 0; i < keys.length; i++) { defineReactive(obj, keys[i]) } } ... } export function defineReactive ( obj: Object, key: string, val: any, customSetter? :? Function, shallow? : boolean ) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // cater for pre-defined getter/setters const getter = property && property.get const setter = property && property.set if ((! getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = ! DefineProperty (obj, key, {// Enumerable: true, // No additional information is configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal ! == newVal && value ! == value)) { return } ... // #7981: for accessor properties without setter if (getter && ! setter) return if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = ! shallow && observe(newVal) dep.notify() } }) }Copy the code

Watcher: SRC/core/observer/Watcher. Js

export default class Watcher { vm: Component; expression: string; cb: Function; id: number; deep: boolean; user: boolean; lazy: boolean; sync: boolean; dirty: boolean; active: boolean; deps: Array<Dep>; newDeps: Array<Dep>; depIds: SimpleSet; newDepIds: SimpleSet; before: ? Function; getter: Function; value: any; constructor ( vm: Component, expOrFn: string | Function, cb: Function, options? :? Object, isRenderWatcher? : boolean ) { this.vm = vm if (isRenderWatcher) { vm._watcher = this } vm._watchers.push(this) // options if (options) { this.deep = !! options.deep this.user = !! options.user this.lazy = !! options.lazy this.sync = !! options.sync this.before = options.before } else { this.deep = this.user = this.lazy = this.sync = false } this.cb = cb this.id = ++uid // uid for batching this.active = true this.dirty = this.lazy // for lazy watchers this.deps = [] this.newDeps = [] this.depIds = new Set() this.newDepIds = new Set() this.expression = process.env.NODE_ENV ! == 'production' ? expOrFn.toString() : '' // parse expression for getter if (typeof expOrFn === 'function') { this.getter = expOrFn } else { this.getter = parsePath(expOrFn) if (! this.getter) { this.getter = noop process.env.NODE_ENV ! == 'production' && warn( `Failed watching path: "${expOrFn}" ` + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ) } } this.value = this.lazy ? undefined : this.get() } /** * Evaluate the getter, and re-collect dependencies. */ get () { pushTarget(this) let value const vm = this.vm try { value = this.getter.call(vm, vm) } catch (e) { if (this.user) { handleError(e, vm, `getter for watcher "${this.expression}"`) } else { throw e } } finally { // "touch" every property so they are all tracked as // dependencies for deep watching if (this.deep) { traverse(value) } popTarget() this.cleanupDeps() } return value } /** * Add a dependency to this directive. */ addDep (dep: Dep) { const id = dep.id if (! this.newDepIds.has(id)) { this.newDepIds.add(id) this.newDeps.push(dep) if (! this.depIds.has(id)) { dep.addSub(this) } } } /** * Clean up for dependency collection. */ cleanupDeps () { let i = this.deps.length while (i--) { const dep = this.deps[i] if (! this.newDepIds.has(dep.id)) { dep.removeSub(this) } } let tmp = this.depIds this.depIds = this.newDepIds this.newDepIds = tmp this.newDepIds.clear() tmp = this.deps this.deps = this.newDeps this.newDeps = tmp this.newDeps.length = 0 } /** *  Subscriber interface. * Will be called when a dependency changes. */ update () { /* istanbul ignore else */ if (this.lazy) { this.dirty = true } else if (this.sync) { this.run() } else { queueWatcher(this) } } /** * Scheduler job interface. * Will be called by the scheduler. */ run () { if (this.active) { const value = this.get() if ( value ! == this.value || // Deep watchers and watchers on Object/Arrays should fire even // when the value is the same, because the value may // have mutated. isObject(value) || this.deep ) { // set new value const oldValue = this.value this.value = value if (this.user) { try { this.cb.call(this.vm, value, oldValue) } catch (e) { handleError(e, this.vm, `callback for watcher "${this.expression}"`) } } else { this.cb.call(this.vm, value, oldValue) } } } } /** * Depend on all deps collected by this watcher. */ depend () { let i = this.deps.length while (i--) { this.deps[i].depend() } } ... }Copy the code

Compiler: SRC/Compiler/index. Js

export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions ): Parse = parse(template.trim(), options) if (options.optimize! > optimize optimize(ast, options)} // 6. Generate code const code = generate(AST, options) options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } })Copy the code

Third, summary

The Observer pattern is the most important JavaScript design pattern, and it is also a frequent interview question. Through the above case analysis, I believe you can fully understand the observer design pattern.

This article is part of the “Gold Nuggets For Free!” Event, click to view details of the event