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.