1. Introduction

We all know that VUE is an excellent framework, and the official website explains that it is an incremental framework. So what is an incremental framework? Progressive is the layering of the framework. As shown in the figure:

As the core part of view layer rendering, one of its characteristics is a responsive system, in which views change with state changes. This is what I like most about Vue. Any place in the view can be represented by a state (variable).

The whole process of generating the DOM from the state and displaying it in the output to the user interface is called rendering. Vue is constantly re-rendering at run time. Responsive systems give the framework the ability to re-render, and an important part of it is change detection. Learning about change detection makes it easier to learn about the principles of the API, so we started implementing a change detection logic from 0 to 1.

2. The directory

3.1 What is Change detection 3.2 How to Track Change 3.3 What are Dependencies and how to collect dependencies 3.4 Where are Dependencies collected 3.5 Who are Dependencies and what is Watcher? 3.6 Recursively detect all key 3.7 object problems

3.1 What is change detection

As mentioned above, rendering is where the Vue automatically generates the DOM from the state and outputs it to the page. The rendering process of Vue is declarative, and we can use templates to describe the mapping between state and DOM. As the web page runs, the data state within the Vue changes through various user interactions, and the page is constantly rendered. But how do we know which states have changed how? This is change detection, and our VUE will know whenever the state changes and render the view with the new state.

3.2 How do I track change

In terms of tracking, in JavaScript, how do we know that an object has changed?

  • Object.defineProperty
  • For ES6 proxies before Vue3, we still used Object.defineProperty

We know that Object.defineProperty has a lot of bugs when it is used to detect changes, and we rewrote this part of the code with Proxy after Vue3, so do we still need to learn this part? In fact, I think it is necessary, after all, we are learning principles and ideas, through the exploration of the principle, we can understand the idea of cattle to solve problems, in the future programming road, or very necessary. Knowing how to track object changes, we can write the following code:

function defineReactive (data, key, val) {
    Object.defineProperty(data, key, {
        enumerable: true.configurable: true.get: function () {
            return val
        },
        set: function (newval) {
            if (val === newval) {
                return
            }
            val = newval
        }
    })
}
Copy the code

We define a function that encapsulates Object.defineProperty. What it does is it defines a responsive data, and when it’s wrapped we just pass data,key, val. So how do you track change? Whenever we read data from the key in data, the get function fires, and when we set data’s key, the set is fired.

3.3 What are dependencies and how can they be collected?

The above encapsulation of Object.defineProperty doesn’t really do anything. What’s really useful is collecting dependencies. Now we have two questions:

  • What is dependency?
  • How do YOU collect dependencies?

So let’s go back and think about what a reactive form is. That’s when the data changes and the view updates automatically. So we look at the data, and when the properties of the data change, we can notify the places where the data has been used. These places are called dependencies, for example:

< the template > < div > < p > {{name}} < / p > / / a dependency < h2 v - text ='name'></h2> // Another dependency </div> </template>Copy the code

In the template, there are two places where the data name is used, so there are two dependencies. When data changes, we send notifications to both dependencies. In change detection, the getter is fired when the data is read, so the dependency is collected in the getter function. When the data changes, the dependency needs to be fired in the setter, so we can modify defineReactive:

function defineReactive (data, key, val) {
    letObject. DefineProperty (Data, key, {enumerable:true,
        configurable: true,
        get: function() {dep.push(window.target) // Add a dependency collection, assuming window.target is a dependencyreturn val
        },
        set: function (newval) {
            if (val === newval) {
                return} val = newval dep.notify() // the notification depends on the data change.Copy the code

3.4 Where is dependency collection

We can encapsulate a class Dep specifically to help us manage dependencies. In this class, we can collect dependencies, delete dependencies, notify dependencies, and so on, with the following code:

export default class dep {
    constructor () {
        this.subs = []
    }
    addSub (sub) {
        this.subs.push(sub)
    }
    removeSub (sub) {
        remove(this.sub, sub)
    }
    depend () {
        if (window.target) {
            this.addSub(window.target)
        }
    }
    notify () {
        const subs = this.subs.slice()
        for (leti=0,l = subs.length; i<1; i++) { subs[i].update() } } }function remove (arr, item) {
    if (arr.length) {
        const index = arr.indexOf(item)
        if (index > -1) {
            return arr.splice(index, 1)
        }
    }
}
Copy the code
  • Subs: Use an array as a container to collect dependencies
  • AddSub: Adds a dependency
  • RemoveSub: Removes dependencies
  • Depend: If window.target is a dependency, determine whether it exists and add the dependency
  • Notify: Notifies each dependency that the data has changed and executes the update view method for each dependency.

Then we need to modify defineReactive:

function defineReactive (data, key, val) {
    letObject. DefineProperty (Data, key, {enumerable:true,
        configurable: true,
        get: function() {dep.depend() // Determine whether there are dependenciesreturn val
        },
        set: function (newval) {
            if (val === newval) {
                return} val = newval dep.notify() // notify each dependency}})}Copy the code

3.5 Who is a dependency and what is watcher?

In the demo above, we used winodw.target to represent a dependency. The effect is that the dependency receives notification when the data changes, and then notifits elsewhere. So we need to encapsulate a class called Watcher. Watcher instances are dependencies on one by one. The code is as follows:

export default class watcher{
  constructor(vm,exp,cb){
    this.vm=vm
    this.getter=parsePath(exp)
    this.cb=cb
    this.value=this.get()
  }
  get(){
    window.target=this
    let value=this.getter.call(this.vm,this.vm)
    window.target=undefined
    return value
  }
  update(){
    const oldValue=this.value
    this.value=this.get()
    this.cb.call(this.vm,this.value,oldValue)
  }
}
Copy the code

Watcher accepts three parameters:

  • Vm: vUE instance
  • Exp :{{}} this expression, as well as the expression in v-text and V-html
  • Cb: This is the real DOM update function.
  • Getter: Parses an expression using the parsePath function to obtain the value of the expression
  • Value: Return value of the get method
  • Get: Obtains the value of an expression through the getter
  • Update: Updates the view

Now that we’ve covered the basic principles of object change detection, if you’re still confused, I’m going to go through the process from the beginning:

Initialization process:

Responsive process:

3.6 Recursively detect all keys

Above, the whole change detection function is nearly implemented, but only one attribute in the data can be detected. We want to be able to detect all attributes in the data, so we need to encapsulate an Observer class. In the form of recursion, all the attributes in the data data become responsive. The code is as follows:

class Observer {
    constructor(value) {
        this.value = value
        if(! Array.isArray(value) { this.walk(value) } } walk (obj) { const keys = Object.keys(obj)for(let i = 0; i < keys.length; i++) {
            definedReactive(obj, keys[i], obj[keys[i]])
        }
    }
}
function definedReactive(data, key, value) {
    if(typeof val === 'object') {
        new Observer(value)
    }
    let dep = new Dep()
    Object.defineProperty(data, key, {
        enumberable: true,
        configurable: true,
        get: function () {
            dep.depend()
            return value
        },
        set: function (newVal) {
            if(value === newVal) {    
                return 
            }
            value = newVal   
            dep.notify()
        }
    })
}
Copy the code

Simple understanding:

3.6 the Object of the problem

Because Object data is tracked through setters/getters, there are grammars in which vUE cannot track even if the data changes. What condition cannot be detected:

  • The new attribute
  • Delete the properties

The solution is via two apis provided by VUE — vm.$set and vM. $delete. We’ll talk about that later.

conclusion

Only understand the principle of change detection, we can learn more about Vue API, also can greatly help us read the source code.

Try an API every day and make more progress every day.