Abstract: The source code interpretation design pattern series will be gradually updated in ~

Observer model

First off, we have to ask ourselves, what is the observer model?

concept

Observer pattern: Often referred to as the publisher-subscriber pattern. It defines a one-to-many dependency, that is, when an object’s state changes, all dependent objects are notified and updated automatically, resolving the coupling of functions between the subject object and the observer.

Tell a story

The above concept of the observer model may be a bit official, so let’s tell a story to understand it.

  • A: It’s A secret agent, code 001.
  • B: A communications officer responsible for the secret handover with A (subscriber)
  1. A Daily job is to gather some information in the open
  2. B is in charge of spying on A
  3. Once A sends some relevant messages (more often, messages need to be encapsulated and transmitted, which will be analyzed according to the source code)
  4. B will immediately subscribe to the message and then make some corresponding changes, such as notification to do something to respond to some action.

applicability

The observer mode can be used in any of the following scenarios

  1. When an abstract model has two aspects, one of which depends on the other. Encapsulating the two in separate objects allows them to be changed and reused independently
  2. When one object is changed, other objects need to be changed at the same time, but it is not known how many objects need to be changed
  3. When an object must notify other objects without knowing who the object is. In other words, you don’t want these objects to be tightly coupled.

Vue’s use of observer mode

Vue uses observer mode in a number of ways, but we’ll focus on data initialization.

var vm = new Vue({
  data () {
    return {
      a: 'hello vue'}}})Copy the code

1. Realize data hijacking

As you can see in the figure above, Vue hijacked the data using Object.defineProperty(). The Dep and Watcher classes encapsulate a layer of hubs when the data changes.

In this section, we will only look at how to hijack data through the observer pattern.

1.1. Recursive traversal

As we all know, Vue has hijacked the data in data, which can only traverse the object to complete the hijacking of each attribute, the source code is as follows

walk (obj: Object) {
  const keys = Object.keys(obj)
  // Iterate over the accessor property to make it vUE
  for (let i = 0; i < keys.length; i++) {
    defineReactive(obj, keys[i], obj[keys[i]])
  }
}
Copy the code

1.2 publish/subscribe

The key to hijacking is defineReactive, which encapsulates getter and setter functions and listens to each other using observer mode

// Set as accessor properties, and in its getter and setter functions, use publish/subscribe mode to listen on each other.
export function defineReactive (
  obj: Object,
  key: string,
  val: any
) {
  // The observer (publish/subscribe) pattern is used to hijack the package. It defines a one-to-many relationship where multiple observers listen to a topic object. When the status of the topic object changes, all observers are notified, and the observer can update its status.
  // Instantiate a topic object with a list of empty observers
  const dep = new Dep()
  
  // Get property descriptor objects (more designed for custom get and set in computed)
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  const getter = property && property.get
  const setter = property && property.set
  
  let childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.// Collect dependencies, establish a one-to-many relationship, let multiple observers listen to the current subject object
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          // This is an array hijacking
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    // Hijack data changes and post notifications
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if(newVal === value || (newVal ! == newVal && value ! == value)) {return
      }
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = observe(newVal)
      dep.notify()
    }
  })
}
Copy the code

1.3. Return to the Observer instance

We saw above that the observe function returns an Observer instance

return new Observer(value)
Copy the code

2. Message encapsulation to realize “relay station”

The first thing to understand is, why do you want a layer of encapsulation for messaging?

We talked about the applicability of the observer model when we talked about it. Similarly, when we hijack data changes and notify them, we don’t know who is subscribing to the message and how many objects are subscribing to the message unless we act as a “hub”.

This is like agent A (publisher) and Agent B (subscriber) in the story I mentioned above. Agent A and AGENT B transmit information. Both of them know the existence of such A person, but Agent A does not know who B is and how many (subscribers) subscribe to him. Many of them may subscribe to agent A’s information. So agent A (publisher) needs to collect all the (subscribers) that subscribe to his messages by means of A secret code, where the collection of subscribers is really A layer of encapsulation. Agent A then simply posts the message, and subscribers receive the notification and do their own update.

Simply put, after collecting the spies from the subscribers, A only publishes the messages, B and more only subscribe the messages and perform the corresponding update operations. Each module ensures its independence and achieves the two principles of high cohesion and low coupling.

Without further ado, how does Vue encapsulate messages

2.1, Dep

The Dep class will be used for Dependency collection. Let’s go straight to the source code

let uid = 0

export default class Dep {
  statictarget: ? Watcher; id: number; subs:Array<Watcher>;

  constructor () {
    // Use Watcher as a unique identifier for each subscriber to prevent repeated collection
    this.id = uid++
    // Define subs array to do dependency collection (collect all subscriber Watcher)
    this.subs = []
  }

  // Collect subscribers
  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
Dep.target = null
Copy the code

The code is short, but what it does is important

  1. Define a subs array to collect the subscriber Watcher
  2. Notify subscriber Watcher to update when hijacking a data change

The source code also throws two methods to operate on dep.target, as follows

// Define the collection target stack
const targetStack = []

export function pushTarget (_target: Watcher) {
  if (Dep.target) targetStack.push(Dep.target)
  // Change the target direction
  Dep.target = _target
}

export function popTarget () {
  // Delete the current target and recalculate the pointer
  Dep.target = targetStack.pop()
}
Copy the code

2.2, the Watcher

Watcher stands for Watcher. What Watcher does is subscribe to the Dep when the Dep sends a notify, so the Watchers subscribing to the Dep do their update. Nonsense not much to say, directly look at the source code to know.

export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function, options? :Object
  ) {
    this.vm = vm
    vm._watchers.push(this)
    this.cb = cb
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      // Parse the expression
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}}}this.value = this.get()
  }

  get () {
    // Collect the target to the target stack
    pushTarget(this)
    const vm = this.vm
    
    let value = this.getter.call(vm, vm)
    // Delete the target
    popTarget()
    
    return value
  }

  // Subscribe to the Dep and let the Dep know that you subscribe to it
  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)) {
        // Collect subscribers
        dep.addSub(this)}}}// The subscriber 'consumes' action, which is performed when changes are received
  update () {
    this.run()
  }

  run () {
    const value = this.get()
    const oldValue = this.value
    this.value = value
    this.cb.call(this.vm, value, oldValue)
  }
}
Copy the code

In the above code, I have deleted some codes irrelevant to the current discussion. If you need to conduct detailed research, you can refer to the source code of vue2.5.3 version by yourself.

Now going back to Dep and Watcher, we need to know two points

  1. DepResponsible for collecting all subscribersWatcherYou don’t have to care who you are, you don’t have to care how many you have, you just have to passtargetTo collect messages subscribed toWatcherThen you just need to get the message outnotifyCan.
  2. WatcherResponsible for the subscriptionDepAnd make sure to subscribeDepCollect and receiveDepWhen you post a message, do it wellupdateOperation.

They seem to depend on each other, but in fact they guarantee their independence and the simplicity of modules.

More applications

Vue also makes use of the “universal” observer pattern in some areas, such as the familiar event passing between components, $ON and $EMIT designs.

$EMIT is responsible for Posting messages and making a unified purchase to the subscriber $ON to execute all events within CBS.

Vue.prototype.$on = function (event: string | Array<string>, fn: Function) :Component {
  const vm: Component = this
  if (Array.isArray(event)) {
    for (let i = 0, l = event.length; i < l; i++) {
      this.$on(event[i], fn)
    }
  } else {
    (vm._events[event] || (vm._events[event] = [])).push(fn)
  }
  return vm
}

Vue.prototype.$emit = function (event: string) :Component {
  const vm: Component = this
  let cbs = vm._events[event]
  if (cbs) {
    cbs = cbs.length > 1 ? toArray(cbs) : cbs
    const args = toArray(arguments.1)
    for (let i = 0, l = cbs.length; i < l; i++) {
      cbs[i].apply(vm, args)
    }
  }
  return vm
}
Copy the code

conclusion

This paper discusses the basic concept of observer mode, its application scenarios, and its specific application in VUE source code. This section summarizes some of the advantages and disadvantages of the observer pattern

  1. Abstract coupling between target and observer: a target only knows that it has a series of observers (the target collects dependencies), but does not know which specific class any of the observers belong to, so the coupling between target and observer is abstract and minimal.
  2. Support broadcast communication: Communication within the observer, unlike other common requests that specify its recipient. Notifications are automatically broadcast to all related objects that have subscribed to the target object, as described abovedep.notify(). Of course, the target object doesn’t care how many objects are interested in it. Its only job is to notify its fellow observers, and it’s up to the observer to process or ignore a notification.
  3. Some unexpected updates: Because an observer itself is unaware of the existence of other observers, it may be ignorant of the ultimate cost of changing the target. Operations performed by the observer directly on the target can cause a series of updates to the observer and the objects that depend on them, so we typically place some operations inside the target to prevent this from happening.

OK, so much for this article, more details of the source code design ideas will be explained in other articles in the same series.

Personal ready to pick up their own public number, after the weekly guarantee of a high quality good article, interested partners can pay attention to a wave.