preface

This article does not scrape the source code, only need basic KNOWLEDGE of JS can follow write. Write along to experience the responsive implementation of Vue2. Let’s get started without further ado.

demand

The first thing to think about is that the general effect of responsiveness is that when the JS code changes a value directly, the corresponding content in the page needs to be updated. So how do you know if the JS code changed a value? Yeah, it’s defineProperty, it’s a method that fires a set method when a value changes, and we can update the page in that set method. And of course, the same thing with Proxy, so I’m going to defineProperty here.

Reactive core — defineProperty

“DefineProperty” MDN link

First encapsulate a method

function defineReactive(obj, key) {
  let val = obj[key]
  Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get: function reactiveGetter () {
      // Why do you want to create another variable to operate
      If obj[key] is returned, the reactiveGetter method will be triggered again, and the doll will recurse until the stack is burst
      return val 
    },
    set: function reactiveSetter (newVal) {
      const value = val
      // The judgment here is to avoid the same value repeatedly triggering subsequent actions
      if(newVal === value || (newVal ! == newVal && value ! == value)) {return
      }
      val = newVal
    }
  })
}
Copy the code

Now that we have the response method, the second problem is how do you know which views to update and how to update them.

If we save an object with all the keys corresponding to the view that we want to update, we can find it when we set it. {key:View}

Sure, but did you ever do that when you wrote about Vue? B: No, it’s too much trouble.

So we’re intercepting the set method and we’re also intercepting the GET method, because when the view is rendered it’s bound to access the variable and trigger the GET method, so we can save the corresponding view relationship in the GET method and use it in the set.

Responsive core design — the Observer pattern

Many people are confused when they hear design patterns, but let’s forget the word for a moment and maybe you’ll understand when we implement responsive.

Let’s implement a class that collects the mappings in GET and updates the views in set

function Dep() {
  this.subs = []// View list
}
Dep.target = null
Dep.prototype.depend = function() {// Collect the mapping
  if(Dep.target) {
    this.subs.push(Dep.target)
  }
}
Dep.prototype.notify = function() {// Notify view updates
  const subs = this.subs.slice()
  for (let i = 0, l = subs.length; i < l; i++) {
    subs[i].update()
  }
}
Copy the code

Dep is short for depend, which means depend.

In particular, the dep. target global property is mentioned here, because in get, there is no way to pass views into it, so it must be passed through a global property. Update is the method that updates the view.

Tweak the defineReactive method

function defineReactive(obj, key) {
  let val = obj[key]
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get: function reactiveGetter () {
      dep.depend()
      return val
    },
    set: function reactiveSetter (newVal) {
      const value = val
      if(newVal === value || (newVal ! == newVal && value ! == value)) {return
      }
      val = newVal
      dep.notify()
    }
  })
}
Copy the code

Then let’s simply implement a VUE

function Vue(options) {
  this._data = options.data
  this.__ob__ = new Observer(this._data)// Set _data to the response
  Dep.target = this // Set the current vue object as the collection object
  this.render()
}
Vue.prototype.render = function () {
  let app = document.getElementById('app')
  app.innerHTML = null
  let title = document.createElement('h4')
  title.innerText = this._data.title
  let content = document.createElement('p')
  content.innerText = this._data.content
  app.appendChild(title)
  app.appendChild(content)
}
Vue.prototype.update = function () {
  this.render()
}
function Observer(obj) {
  this.obj = obj
  const keys = Object.keys(obj)
  for (let i = 0; i < keys.length; i++) {// Iterate over the key and turn the attribute into a response
    defineReactive(obj, keys[i])
  }
}
Copy the code

The HTML file

<! DOCTYPEhtml>
<html>
  <body>
    <div id="app"></div>
  </body>
</html>
<script src="index1.js"></script>
<script>
  let vm = new Vue({
    data: {
      title: 'Hello world! '.content: 'Tech change the world'}})</script>
Copy the code

Ok, let’s see what happens

Nice, the basic effect is there. But here comes the first problem.

When we print the subs array in the dep.prototype. notify method, we find that the same vue object has been added repeatedly. In fact, we only need to add vue objects during the first render, so we need to leave dep. target blank after render in the vue method

function Vue(options) {
  this._data = options.data
  this.__ob__ = new Observer(this._data)// Set _data to the response
  Dep.target = this // Set the current vue object as the collection object
  this.render()
  Dep.target = null
}
Copy the code

Ok problem solved.

The next problem is that we want it to be a little bit more specific, not to take vue objects and manipulate them, so let’s create a new class

function Watcher(vm) {
  this.vm = vm
  Dep.target = this
  vm.render()
  Dep.target = null
}
Watcher.prototype.update = function() {
  this.vm.render()
}
Copy the code

This is the observer, get a feel for the logic of the character. Observe data changes and notify view updates.

Adjust the VUE method

function Vue(options) {
  this._data = options.data
  this.__ob__ = new Observer(this._data)
  this.watcher = new Watcher(this)}Copy the code

Ok.

I think you get the idea of the observer pattern.

The sample code