[Front End Advanced Foundation] VUE Responsive Data Principle Subscription-Publishing Mode Analysis

  javascript, vue.js

Two Abstract Cores of vue Framework: Virtual DOM and Corresponding Data Principle

As for the core algorithm of virtual DOM, we have basically analyzed it in the previous chapter. See for details.
Reach &&vue virtualdom’s Diff algorithm unification path snabbdom.js interpretation

Regarding the principle of responsive data, let’s look at the picture first.
You’ (4). PNG
clipboard.png

Specifically, it should be divided into the following steps:

  • When initializing an instance object, run initState, set up hooks for props, data and Observer for its object members. for the computed attribute, set up all corresponding Watcher and set up a getter for this attribute on the vm object through Object.defineProperty At the same time, the corresponding watcher is established according to the customized Watch.
  • Perform the mount operation, establish a Watcher directly corresponding to render during mount, compile the template to generate render function, and execute vm._update to update DOM.
  • After that, whenever there is any data change, the corresponding Watcher will be notified to execute the callback function.

Observer hijackers

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 is Object.
 */
 walk (obj: Object) {
 const keys = Object.keys(obj)
 for (let i = 0;   i < keys.length;  i++) {
 defineReactive(obj, keys[i], 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])
 }
 }
 }

First of all, we observed that when new Observer (), we would make type judgment.

if (Array.isArray(value)) {
 const augment = hasProto
 ?  protoAugment
 : copyAugment
 augment(value, arrayMethods, arrayKeys)
 this.observeArray(value)
 } else {
 this.walk(value)
 }

If it is an array type, it will be recursively called to establish a dependency system.
Otherwise, call the walk () function to take advantage of the Object.defineProperty resume dependency.

How is defineReactive implemented, as follows

//Some codes are omitted
 export function defineReactive (
 obj: Object,
 key: string,
 val: any,
 customSetter?  : ?  Function,
 shallow?  : boolean
 ) {
 const dep = new Dep()
 ...
 Object.defineProperty(obj, key, {
 ...
 get: function reactiveGetter () {
 ...
 dep.depend()
 ...
 return value
 },
 set: function reactiveSetter (newVal) {
 ...
 dep.notify()
 }
 })
 }

Interceptor will set middleware on getter and setter respectively to maintain dependencies in dep array

  • dep.depend()
  • dep.notify()

The literal meaning is very obvious, which is used to increase dependency and notify dependency change relation respectively.

Dependency//dependency

class Dep {
 static target: ?  Watcher;
 id: number;
 subs: Array<Watcher>;
 
 constructor () {
 this.id = uid++
 this.subs = []
 }
 
 addSub (sub: Watcher) {
 this.subs.push(sub)
 }
 
 removeSub (sub: Watcher) {
 remove(this.subs, 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()
 }
 }
 }

Dep is a data dependency corresponding to Watcher, and there is also a subs array in this object to store Watcher related to this dependency. Its member functions are mainly dependent and notify, the former is used to set a Watcher’s dependency, the latter is used to notify the Watcher related to this dependency to run its callback function.

We know that data bound by instructions or double braces on Dom will add observer Watcher to the data. when the watcher is instantiated, the getter method of the attribute will be triggered, and dep.depend () will be called at this time. Let’s look at the depend method:

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

What is Dep.target? In fact, when the Watcher is instantiated, the internal get function will be called to start initialization for him.

Watcher observer

The pushTarget method is to bind this watcher instance for Dep.target, so Dep.target.addDep(this) is to execute the addDep method in this instance.

addDep (dep: Dep) {
 ...
 dep.addSub(this)
 }

This adds a watcher instance to our dep instance.

Then when we update data, we will trigger his set method and execute the dep.notify () function:

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

This is to traverse the watcher instance collected in dep and update (). That is to say, the data update operation is carried out. This is also a simple data response formula. In fact, it should be noted that when the getter of the data triggers, dependencies will be collected, but not all trigger methods will collect dependencies. Only the getter triggered by the watcher will collect dependencies: if (Dep.target) {dep.depend()}, and the so-called collected dependencies are the current watcher. Data in DOM must be bound by the watcher and read only by the watcher.

Finally pay a function timeline
Png
clipboard.png