preface

Object.defineproperty () if you’ve read the source code for Vue, or if you’ve read how Vue responds, you know that Object.defineProperty() is used to monitor data in Vue 2.x by recursively iterating through data objects. When we use object.defineProperty (), we set the index of the array directly and cannot respond in real time because object.defineProperty () cannot detect the index change of the array. And the array methods that we normally use, push, pop, shift, unshift, splice, sort, reverse, are not really array methods, It is modified because object.defineProperty () provides limited capabilities and can’t be perfect.

Read many online about Vue source interpretation or implementation of a simple version of Vue tutorial, Object.defineproperty () does have a lot of limitations. It is said that the 3.x version of Vue will use Proxy, so today we are going to try it. Use Proxy to implement a simple version of Vue

The proxy is introduced

Proxy is used to modify the default behavior of certain operations, which is equivalent to making changes at the language level. Therefore, it is a kind of “meta programming”, that is, programming a programming language.

Proxy can be understood as a layer of “interception” before the target object. All external access to the object must pass this layer of interception. Therefore, Proxy provides a mechanism for filtering and rewriting external access. The word Proxy is used to mean that it acts as a Proxy for certain operations.

The above quotes are from the Proxy chapter of the ES6 tutorial by Yifeng Ruan.

Let’s see how we can use Proxy to define a function that listens for data. Okay

Define the observe

 _observe (data){
    var that = this
    
    // Store the object returned by the proxy in this.$data
    this.$data = new Proxy(data, {
    set(target,key,value){
      // Use Reflect to restore the default assignment
      let res =  Reflect.set(target,key,value)
      // This line is the monitor code
      that.handles[key].map(item= > {item.update()})
      return res
    }
    })
}
Copy the code

That.handles[key].map(item => {item.update()}) executes all the “monitors” in that attribute’s name when a set is triggered

So, where did the monitor come from? Continue with the code below

Define the compile

  _compile (root){
       const nodes = Array.prototype.slice.call(root.children)
       let data = this.$data
       nodes.map(node= > {
         // If it is not the last node, recurse
         if(node.children.length > 0) this._complie(node)
         // Handle the V-bind directive
         if(node.hasAttribute('v-bind')) {
           let key = node.getAttribute('v-bind')
           this._pushWatcher(new Watcher(node,'innerHTML',data,key))
         }
         // Process the V-model directive
         if(node.hasAttribute('v-model')) {
           let key = node.getAttribute('v-model')
           this._pushWatcher(new Watcher(node,'value',data,key))
           node.addEventListener('input',() => {data[key] = node.value})
         }
         // Handle the V-click directive
         if(node.hasAttribute('v-click')) {
           let methodName = node.getAttribute('v-click')
           let mothod = this.$methods[methodName].bind(data)
           node.addEventListener('click',mothod)
         }
       })
     }
Copy the code

The code above looks long, but in fact, all it does is “compile” the HTML template, adding the corresponding notifications and monitoring for v-bind, V-model, and V-click

  1. Pushwatcher (…) $this._pushWatcher () {pushwatcher () {this._pushWatcher () {this._pushWatcher ()

  2. Monitoring is node.addeventListener (…) Listen for the corresponding event, and then execute the corresponding Watcher or methods

So what does this._pushWatcher do?

 _pushWatcher (watcher) {
      if (!this._binding[watcher.key]) this._binding[watcher.key] = []
      this._binding[watcher.key].push(watcher)
    }
Copy the code

If this._binding[watcher.key] is null, initialize it and add a listener to it

And finally, how does a listener work

Define the Watcher

 class Watcher {
     constructor (node,attr,data,key) {
       this.node = node
       this.attr = attr
       this.data = data
       this.key = key
     }
     update () {
       this.node[this.attr] = this.data[this.key]
     }
   }
Copy the code

Watcher is a class with only one method, which is to update data. How to know which node to update and what data to update? When instantiated (new), the passed parameters define these

So we have our initial bidirectional binding, which is about 50 lines of code. It could have been less, but less would have been a continuation of the castration feature (although the current implementation is also a heavily castrated version), but I think it would have helped us understand the nature of VUE.

The last

The final implementation code of this article has been put on Github, students who want to see the effect directly, can go up directly copy, run.

If you find this article useful, please add a star to github of this article. Thank you very much

In addition, there are other front-end tutorials and components available on Github for those who are interested. Your support is my biggest motivation.