What is change detection
The function of change detection is to detect data changes. When the data changes, it will notify the view to update accordingly
When running, the internal state of the application will constantly change, and you need to re render constantly. How do you determine what changes have taken place in the state? Change detection is mainly used to solve this problem.
Vue's change detection belongs to push. When the state changes, Vue immediately knows, to some extent, which states have changed.
A state binds multiple dependencies, and each dependency represents a specific DOM node. When the state changes, a notification is sent to all dependencies in this state to update the dom. The more dependencies per state binding, the greater the memory overhead of dependency tracking.
Virtual DOM has been introduced since vue.js 2.0. The dependency bound by a state is no longer a specific DOM node, but a component. When the state changes, the component will be notified, and the virtual DOM is used for comparison within the component. This can greatly reduce the number of dependencies, thereby reducing the memory consumed by dependency tracking.
Object change detection
-
Data is converted into getter/setter form through Observer to track changes
-
When the outside world reads data through the watcher, the getter will be triggered to add the watcher to the dependency
-
When the data changes, the setter will be triggered to send a notification to the dependency in Dep.
-
After receiving the notification, the watcher will send a notification to the outside world. After the change notification is sent to the outside world, the view update may be triggered, or a callback function of the user may be triggered.
How to track changes
How to detect the change of an object in vue? Proxy using Object.defineProperty and ES6
function defineReactive(data,key,val){ Object.defineProperty(data,key,{ enumerable: true, configuration: true, get: function(){ return val }, set: function(newVal){ if(val === newVal){ return } val = newVal } }) }
Whenever data is read from the key of data, the get function is triggered; Whenever data is set in the key of data, the set function is triggered.
How to collect dependencies
The purpose of observing data is to inform those places where the data has been used when the attributes of the data change.
<template> <h1>{{name}}</h1> </template>
The template uses the data name, so when it changes, you should send a notification to the place where it is used.
Collect dependencies: collect the data where the data name is used, and then trigger the previously collected dependency cycle when the attribute changes.
Where is the dependency collection
Collect dependencies in getter s and trigger dependencies in setter s
dep class is dedicated to managing dependencies. Using this class, you can collect dependencies, delete dependencies, or send notifications to dependencies.
export default class Dep { constructor () { this.subs = [] } addSub (sub) { this.subs.push(sub) } // Delete a dependency removeSub (sub) { remove(this.subs, sub) } // Add a dependency depend () { if (window.target) { this.addSub(window.target) } } // Notify all dependent updates notify () { const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } }
function defineReactive (obj,key,val) { const dep = new Dep() //Instantiate a dependency manager to generate a dependency management array dep Object.defineProperty(obj, key, { enumerable: true, configurable: true, get(){ dep.depend() // Collect dependencies in Getters return val; }, set(newVal){ if(val === newVal){ return } val = newVal; dep.notify() // Notify dependency updates in setter } }) }
Who is dependence?
Who needs to be notified when the attribute changes
In fact, Vue also implements a class called Watcher, and the instance of Watcher class is the "who" we mentioned above. In other words, we create a Watcher instance for those who use data and those who rely on it. When the data changes later, we do not directly notify the dependency update, but notify the corresponding Watch instance, and the Watcher instance notifies the real view.
export default class Watcher { constructor (vm,expOrFn,cb) { this.vm = vm; this.cb = cb; this.getter = parsePath(expOrFn) this.value = this.get() } get () { window.target = this; const vm = this.vm let value = this.getter.call(vm, vm) window.target = undefined; return value } update () { const oldValue = this.value this.value = this.get() this.cb.call(this.vm, this.value, oldValue) } } /** * Parse simple path. * Take the value represented by a string path in the form of 'data.a.b.c' from the real data object * For example: * data = {a:{b:{c:2}}} * parsePath('a.b.c')(data) // 2 */ const bailRE = /[^\w.$]/ export function parsePath (path) { if (bailRE.test(path)) { return } const segments = path.split('.') return function (obj) { for (let i = 0; i < segments.length; i++) { if (!obj) return obj = obj[segments[i]] } return obj } }
Recursively detect all key s
The previous code can only detect a certain attribute in the data. We want to detect all attributes in the data, so we need to encapsulate an Observer class. The function of this class is to convert all attributes in a data into getter/setter form, and then track their changes.
function Observer (value) { this.value = value; if (!Array.isArray(value)) { this.walk(value); } }; Observer.prototype.walk = function walk (obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive$$1(obj, keys[i],obj[keys[i]]); } }; function defineReactive (obj,key,val) { //New, recursive sub attribute if(typeof val === 'object'){ new Observer(val); } const dep = new Dep() //Instantiate a dependency manager to generate a dependency management array dep Object.defineProperty(obj, key, { enumerable: true, configurable: true, get(){ dep.depend() // Collect dependencies in Getters return val; }, set(newVal){ if(val === newVal){ return } val = newVal; dep.notify() // Notify dependency updates in setter } }) }
deficiencies
Although we can observe the object data through the Object.defineProperty method, this method can only observe the value and setting value of the object data. When we add a pair of new key / values to the object data or delete a pair of existing key / values, it cannot be observed. As a result, when we add or delete values to the object data, Cannot notify dependencies and drive views for reactive updates.
Of course, Vue has also noticed this. In order to solve this problem, Vue has added two global APIs: vue.set and Vue.delete.