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

  1. Read the source code to see how change tracking is implemented in VUE
  2. Implement a simple VUE for simple reactive and dependent collection
  3. 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