This is a small demo in the video, the code is very simple but the idea is very clear ~

This is a simple implementation of the core principles of Vue 1.x. Although there are many improvements in Vue 2.x, the principles used here to analyze Vue responsiveness are sufficient.

demo

Suppose we want to implement a Vue class that initializes the DOM and implements data-view responsiveness with a call like this:

// index.html<body> <div id="app"> <p>{{ name }}</p> <p v-text="name"></p> <br> <p>{{ age }}</p> <br> <input type="text" V-model ="name"> <br> <div v-html=" HTML "></div> <br> <button @click="changeName"> El: '#app', data: {name: "I am initializing ", age: 12, HTML: '< p > I'm a p < / p >'}, mounted () {setTimeout (() = > {enclosing name = 'I am one point five seconds delay enclosing HTML =' < p > the mew: ahaha ha ha ha ha < / p > '}, 1500)}, Methods: {changeName() {this.name = 'age'}}}) </script> </body>Copy the code

Observer model

First implement an observer pattern:

/ / theme
class Dep {
  constructor() {
    this.watchers = []
  }

  addDep(watcher) {
    this.watchers.push(watcher)
  }

  notify() {
    this.watchers.forEach(watcher= > watcher.update())
  }
}
/ / observer
class Watcher {
  constructor(vm, key, cb) {
    this.vm = vm
    this.key = key
    this.cb = cb

    Dep.target = this
    this.vm[key]
    Dep.target = null
  }

  update() {
    this.cb.call(this.vm, this.vm[this.key])
  }
}
Copy the code

The Dep class is mainly used to collect dependencies (addDep) and distribute updates (notify).

The main purpose of the Watcher class is to trigger the hooks associated with attributes in data (the get method in data hijacking, described below). New Watcher assigns dep. target to the Watcher instance and this.vm[key] triggers the get hook to be added to the dependency. The Update method on the Watcher instance is the actual dom update logic.

Data hijacking and responsiveness

class Vue {
  constructor(options) {
    this.observe(options.data)
  }

  observe(data) {
    if(! data || ! (typeof data === 'object')) return
    Object.entries(data).forEach(([key, value]) = > {
      this.observe(value) // recursive traversal
       `this.defineReactive(data, key, value)`})},defineReactive(data, key, value) {
    const dep = new Dep()
    Object.defineProperty(data, key, {
      get() {
        Dep.target && dep.addDep(Dep.target)
        return value
      },
      set(newV) {
        value = newV
        dep.notify()
      }
    })
  }
}
Copy the code

The Vue class performs a deep loop over data at initialization and calls this.definereactive (data, key, value). The value DEP equivalents are saved inside the defineReactive function using closure technology for reading and writing when data changes.

You may ask, how does this achieve data response?

The answer is the Dep class and Watcher class. The defineReactive function creates a DEP instance when it is executed and adds a Watcher instance for dependency collection when the data attribute gets.

Each attribute of data in defineReactive corresponds to a DEP instance that holds all observers associated with the attribute (the attribute may be referenced multiple times in the page, as in the case of name appearing multiple times in the HTML template). Each time this property is modified, dep.notify() is triggered to notify all relevant observer instances that the update method is invoked.

Now that the principle of reactive is clear, when did Vue create watcher instances?

Dom compilation and Watcher initialization

In our Dome, when the Vue was initialized, #app was a real DOM full of {{}}, so we compiled it.

What you’re doing here is taking the real DOM from #app, replacing the {{}} with real data and binding it in a reactive way so that it updates automatically.

class Compile {
  constructor(el, vm) {
    this.$el = document.querySelector(el)
    this.$vm = vm

    const fragment = this.node2Fragment(this.$el)
    this.compile(fragment)
    this.$el.appendChild(fragment)
  }
  node2Fragment(el) {
    const fragment = document.createDocumentFragment()
    let child
    while (child = el.firstChild) {
      fragment.appendChild(child)
    }
    return fragment
  }
  compile(el) {
    Array.from(el.childNodes).forEach(node= > {
      if (node.nodeType === 1) {
        / / v - HTML @ the click, etc
        this.compileElement(node)
      } else if (this.isInter(node)) {
        // is a text node
        this.compileText(node)
      }
      node.children && this.compile(node)
    })
  }
  isInter(node) {
    return /\{\{\s*([^\s]+)\s*\}\}/.test(node.textContent)
  }
  compileText(node) {
    The regex capture group in the this.isInter function caches the matched characters into RegExp.$1. Here the characters in {{}} are matched
    const key = RegExp.$1
    node.textContent = this.$vm[key]
    new Watcher(this.$vm, key, function(value) {
      node.textContent = value
    })
  ... 
}
Copy the code

When the Compile class is initialized, the node2Fragment function is first called to extract the DOM from #app and mount it under a fragment. The fragment is like a virtual node that disappears when mounted to the real DOM, leaving only the child nodes.

The compile function is then called to recursively iterate over the children of the fragment (childNodes, because children does not contain text comments, etc.) and determine whether they are text nodes or node nodes, performing different processing.

If this.isinter (node) is judged to be a text node, execute compileText to update the node text to the corresponding value in data. It then instantiates an observer, logging the attribute and its associated DOM update logic. Data hijacking is triggered during watcher initialization, adding the Watcher instance to the corresponding dependency.

class Compile {
  
  compileText(node) {
    The regex capture group in the this.isInter function caches the matched characters into RegExp.$1. Here the characters in {{}} are matched
    const key = RegExp.$1
    node.textContent = this.$vm[key]
    new Watcher(this.$vm, key, function(value) {
      node.textContent = value
    })
  }
}
Copy the code

conclusion

So far, the responsivity principle of Vue has been basically clarified. There are two main steps:

  1. Observe hijacks data and initializes a topic DEP instance for each property
  2. Compile replaces slots in the template with real data and initializes an observer watcher instance

Data and views are updated in a responsive manner through the observer mode.

Demo also has V-model, @click, etc., willing to realize or lazy: portal