We learned about the responsivity principle of Vue earlier, we know that vue2 is based on Object. DefineProperty to implement data responsivity, but this alone is not enough, the data we define in data may not be used for template rendering, modifying these data will also set to cause re-rendering. So VUE is optimized here to collect dependencies to determine which data changes need to trigger view updates.

preface

If this article is helpful to you, ❤️ attention + like ❤️ to encourage the author, the article public number first, pay attention to the front of nanjiu first time to get the latest article ~

Let’s start with two questions:

  • 1. How do we know where to use the data in data?
  • 2. How to notify Render to update view when data changes?

During view rendering, the data used needs to be recorded, and only changes to that data trigger view updates

This requires doing dependency collection, creating a DEP for the property to collect the render Watcher

Collect as Dependency = dep.Depend (); Notify = dep.notify()

Depend on the individual classes in the collection

There are three classes responsible for dependency collection in the Vue source code:

  • Observer: an observable class that converts arrays/objects into observable data. Each Observer instance member has an instance of Dep (this class was implemented in the previous article)

  • Dep: Observe the target class. Every single data has an instance of Dep class (subscribers already have subs in the queue). When the data is changed, call DEP.notify () to notify the observer

  • Watcher: Observer class that wraps the observer function. The Render () function, for example, is wrapped as a Watcher instance

A dependency is a Watcher, and only the getter triggered by the Watcher will collect the dependency. The Watcher that triggered the getter will collect the dependency into the Dep. Dep uses a publishare-subscribe model, which loops through the list as data changes, notifies all watchers, and I’ve drawn a clearer picture myself:

The Observer class

We already implemented this class in the last installment. The main addition in this installment is defineReactive for dependency collection when the data G ētter is hijacked, and for notifying dependency updates when the data setter is hijacked. Here is the entry for Vue to collect dependencies

class Observer {
     constructor(v){
         // Each Observer instance has a Dep instance
         this.dep = new Dep()
        // If there are too many data layers, the attributes in the object need to be recursively resolved, adding set and GET methods in order
        def(v,'__ob__'.this)  // Attach an __ob__ attribute to the data to indicate observation
        if(Array.isArray(v)) {
            // Reattach the overridden array method to the array prototype
            v.__proto__ = arrayMethods
            // Check if the array contains objects
            this.observerArray(v)
        }else{
            // Call defineReactive directly to define data as responsive objects
            this.walk(v)
        }
        
     }
     observerArray(value) {
         for(let i=0; i<value.length; i++) { observe(value[i]) } }walk(data) {
         let keys = Object.keys(data); // Get the object key
         keys.forEach(key= > {
            defineReactive(data,key,data[key]) // Define a responsive object}}})function  defineReactive(data,key,value){
     const dep = new Dep() // Instantiate deP to collect dependencies and notify subscribers of updates
     observe(value) // Implement depth monitoring recursively, pay attention to performance
     Object.defineProperty(data,key,{
         configurable:true.enumerable:true.get(){
             / / get the value
             // If you are in the dependent mobile phase
             if(Dep.target) {
                 dep.depend()
             }
            // Rely on collection
            return value
         },
         set(newV) {
             / / set the value
            if(newV === value) return
            observe(newV) // To continue hijacking newV, the user may set the new value as an object
            value = newV
            console.log(The 'value changes :',value)
            // Publish subscription mode, notification
            dep.notify()
            // cb() // Subscriber receives message callback}})}Copy the code

Attach an instance of the Observer class to the __ob__ property for later data observation, instantiate the Dep class instance, and save the object/array as the value property – if the value is an object, perform the walk() procedure, The traversal object makes each item observable (called defineReactive) – if the value is an array, perform the observeArray() procedure, recursively calling observe() on the array element.

Dep Class (Subscriber)

The Dep class is used to store Watcher observer objects. Each data has an instance of the Dep class. There can be multiple observers in a project, but because JavaScript is single-threaded, only one observer can be executing at a time. The Watcher instance of the observer being executed at the moment is assigned to the dep. target variable, so you can see who the observer is by simply accessing dep. target.

var uid = 0
export default class Dep {
    constructor() {
        this.id = uid++
        this.subs = [] // Subscribes to Watcher
    }

    // Collect observers
    addSub(watcher) {
        this.subs.push(watcher)
    }
    // Add dependencies
    depend() {
        // The global location that you specify is globally unique
      // dep. target = the instance of Watcher
        if(Dep.target) {
            this.addSub(Dep.target)
        }
    }
    // Tell the observer to update
    notify() {
        console.log('Notify observer of updates ~')
        const subs = this.subs.slice() // Make a copy
        subs.forEach(w= >w.update())
    }
}
Copy the code

Dep is actually the management of Watcher, and it makes no sense for Dep to exist independently of Watcher.

  • DepIs a publisher that can subscribe to multiple observers, depending on the collectionDepThere will be one of themsubsStore one or more observers and notify all of them when data changeswatcher.
  • DepandObserverThe relationship betweenObserverListen on the entire data, traversing each property of data to bind each propertydefineReactiveMethods hijackedgetterandsetterIn thegetterWhen theDepClass riser dependency(dep. Depend)In thesetterNotify allwatcherforupdate(dep.notify).

Watcher class (Observer)

The Watcher class is an observer that cares about the data, gets notified when the data changes, and updates it through callback functions.

As you can see from the Dep above, Watcher needs to implement the following two functions:

  • dep.depend()Add yourself to subs
  • dep.notify()Is called whenwatcher.update()To update the view

Also note that there are three types of watcher: Render Watcher, computed Watcher, and User Watcher (the watch in the VUE method)

var uid = 0
import {parsePath} from ".. /util/index"
import Dep from "./dep"
export default class Watcher{
    constructor(vm,expr,cb,options){
        this.vm = vm // Component instance
        this.expr = expr // The expression to observe
        this.cb = cb // The callback function when the observed expression changes
        this.id = uid++ // The unique identifier of the observer instance object
        this.options = options // Observer option
        this.getter = parsePath(expr)
        this.value = this.get()
    }

    get(){
        // Depending on the collection, set the global dep. target to Watcher itself
        Dep.target = this
        const obj = this.vm
        let val
        // Keep looking as long as you can
        try{
            val = this.getter(obj)
        } finally{
            // After collecting dependencies, set dep. target to null to prevent dependencies from being added repeatedly.
            Dep.target = null
        }
        return val
        
    }
    // Trigger updates when dependencies change
    update() {
        this.run()
    }
    run() {
        this.getAndInvoke(this.cb)
    }
    getAndInvoke(cb) {
        let val = this.get()

        if(val ! = =this.value || typeof val == 'object') {
            const oldVal = this.value
            this.value = val
            cb.call(this.target,val, oldVal)
        }
    }
}
Copy the code

Note that watcher has a sync attribute. In most cases, watcher is not updated synchronously, but asynchronously, by calling queueWatcher(this) and pushing it to the observer queue for nextTick.

The parsePath function is a higher-order function that parses an expression into a getter, that is, a value. We can try it out:

export function parsePath (str) {
   const segments = str.split('. ') // Start with the expression. Cut into a single piece of data
  // It returns a function
  	return obj = > {
      for(let i=0; i< segments.length; i++) {
        if(! obj)return
        // Iterate over the expression to fetch the final value
        obj = obj[segments[i]]
      }
      return obj
    }
}
Copy the code

Dep’s relationship with Watcher

The DEP is instantiated in watcher and subscribers are added to dep.subs, which notifies each Watcher of updates through notify through dep.subs.

conclusion

Depend on the collection

  1. initState When the computedProperty is triggered when initializedcomputed watcherDepend on the collection
  2. initStateIs triggered when the listening property is initializeduser watcherRely on collection (watch)
  3. render()When the triggerrender watcherDepend on the collection
  4. re-renderWhen,render()If executed again, all will be removedsubsIn thewatcerSubscribe to, and reassign.
observe->walk->defineReactive->get->dep.depend()->
watcher.addDep(new Dep()) -> 
watcher.newDeps.push(dep) -> 
dep.addSub(new Watcher()) -> 
dep.subs.push(watcher)
Copy the code

Distributed update

  1. Component changes the data in the responsedefineReactiveIn thesetterThe logic of the
  2. And then calldep.notify()
  3. Go through all of them one last timeSubs (Watcher instance), call each of themwatcherupdateMethods.
set -> 
dep.notify() -> 
subs[i].update() -> 
watcher.run() || queueWatcher(this) -> 
watcher.get() || watcher.cb -> 
watcher.getter() -> 
vm._update() -> 
vm.__patch__()
Copy the code

Recommended reading

  • 【Vue source code learning 】 responsive principle exploration
  • Causes and solutions of unreliable JS timer execution
  • From how to use to how to implement a Promise
  • Explains the page loading process in great detail

The original original address point here, welcome everyone to pay attention to the public number “front-end South Jiu”.

I’m Nan Jiu, and we’ll see you next time!!