preface
As is known to all, vue is a data driven framework, from the data changes to the page content changes in the process of a view is presented to the user, and data is a dynamic change, change is likely to be on this side of the backend interface returned to the change of the data, it may also be because the page changes in the operation, all in all, under the change of the data, page elements will produce certain response, The problem is that the page is re-rendered if it knows that the data has changed
Learning goals
- Read the source code to see how change tracking is implemented in VUE
- Implement a simple VUE for simple reactive and dependent collection
- Draw a flowchart for tracing data to dependency collection in VUE
How are changes in data collected
Using Object.defineProperty(), the API defines getters and setters for data to know when data is fetched and when it is modified. So when implementing basic Vue data tracing, We need to convert each data in the data passed to the VUE instance in a reactive manner
Class Vue {constructor(options) {constructor(options) {this.$options = options this._data = options = Object.keys(this._data) for(var i = 0; i < list.length; i++) { this.observe(list[i], this._data, this._data[list[i]], this) } } observe(key, data, val, DefineReactive (key, data, val,vm) {// Convert each attribute to reactive this.definereactive (key, data, val,vm)} defineReactive(key, data, val, vm) { Object.defineProperty(data, key, { configurable: true, enumerable: true, get: function() { console.log(this, 'get') return val }, set: function(newVal) { console.log(this, 'set') // vm._data[key] = newVal val = newVal dep.notify() } }) } }Copy the code
How to collect dependencies
Define a dependency manager or dependency collector for every data that is converted to responsiveness, manage where every data is used, and update every place in the dependency manager when the data changes, so that changes can be made on the data change page
class Dep {
constructor() {
this.uid = new Date().getTime()
this.subs = []
}
add(watcher) {
this.subs.push(watcher)
}
notify() {
this.subs.map(watcher => {
watcher.update()
})
}
}
Copy the code
And initialize a dependency manager when converting data to responsiveness
What is dependence
A dependency is understood in VUE as a listener, a wathcer, which has the ability to update a page. When a property is used in multiple places, each place has a dependency
class Watcher {
constructor(vm) {
this.vm = vm
this.get()
}
update() {
this.vm.render()
}
get() {
window.target = this
}
}
Copy the code
Where do you collect dependencies
In the Vue source code, the instance is initialized when it is mounted, and the initialized watcher is passed the value represented by the current string path, which triggers the getter method of the data, at this time to collect the dependency, and put the dependency into the dependency manager DEps
As above, we collect dependencies and update them as the data changes
The complete code
<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, Initial scale=1.0"> <title>Document</title> </head> <body> <div v-text="text"></div> </body> <script> class Vue { Constructor (options) {this.$options = options this._data = options Object.keys(this._data) for(var i = 0; i < list.length; i++) { this.observe(list[i], this._data, this._data[list[i]], this) } this.watch(this, this.render.bind(this)) } render() { var query = document.querySelectorAll('[v-text]') var key = query[0].getAttribute('v-text') let data = this._data[key] query[0].innerHTML = this._data[key] } watch(vm, render) { window.target = new Watcher(this) return render() } observe(key, data, val, vm) { this.defineReactive(key, data, val,vm) } defineReactive(key, data, val, vm) { let dep = new Dep() Object.defineProperty(data, key, { configurable: true, enumerable: true, get: function() { console.log(this, 'get') if (window.target) { dep.subs.push(window.target) window.target = null } return val }, set: function(newVal) { console.log(this, 'set') // vm._data[key] = newVal val = newVal dep.notify() } }) } } class Dep { constructor() { this.uid = new Date().getTime() this.subs = [] } add(watcher) { this.subs.push(watcher) } notify() { this.subs.map(watcher => { watcher.update() }) } } class Watcher { constructor(vm) { this.vm = vm this.get() } update() { this.vm.render() } get() { window.target = this } } var demo = new Vue({ el: '#demo', data: { text: '123123' } }) </script> </html>Copy the code
conclusion
Object.defineproperty () is the core API used in Vue 2.0. After we implement listening on an Object, we do another processing on data listening in VUE, mainly hijacking and modifying the native method of array. ‘pop’,’shift’,’unshift’,’splice’,’sort’,’reverse’, so we don’t change the value of a subscript directly when manipulating data. Instead, we use the native method or $set(split). For dependent collection, We need to know when to collect dependencies, where to put them after they are collected, and when to update the dependency process. We can also learn what is the publisher subscriber pattern + data hijacking
Depend on the main flow of collection
1. Redefine getters and setters for data via defineProperty
2. Instantiate a Watcher when the page is mounted or the data is read, and collect the dependencies in the DEps by triggering the getter for the data through the get method in Wathcer
3. When the data changes, the setter for the data is triggered and the DEPS is triggered to update the dependency, causing the page to change