preface

This paper adopts the observer mode in design mode to simulate the effect of two-way data binding in Vue. If you are not familiar with the observer mode, you can first read “JS design Mode | Observer mode”, this article only simulated the effect of two-way data binding, and Vue source has a big difference

Implementation effect

DOM

Write DOM using Vue syntax rules

2.0 using the Vue

Use native Vue 2.0 to achieve two-way data binding

Analog implementation

myVueClass USES

myVueClass implements

MyVue class to achieve the data monitoring of data, DOM simple parsing

The observeddata

The _obverse method transforms data into an observable using the object.defineProperty () method

And register the data object property name as the key of the notification event into the _directives object to notify the bound observer of the update when the value update in data is implemented

Call the myVue class as described in “myVue Class Use”, and the properties in this._directives are shown in the following figure after the this._obverse() method is executed in the myVue class

Watcher class

Declare the Watcher Watcher class to execute the _update() method in the Watcher instance when data is updated to update data on the bound DOM

DOM parsing methods

The _compile() method in myVue is used to parse DOM nodes

Traverse the DOM node

Parse the V-text attribute on the DOM node, bind Watcher listeners for the corresponding node, and update them via the _update() method in Watcher when the listener value changes

The V-model attribute on DOM node is parsed, and input input listener is registered for this node. When input is input, the corresponding value in data is updated, and Watcher listener is bound on this node to update the value to the value attribute of DOM node for value synchronization

Call the myVue class as described in “myVue Class Use”, and the properties of this._directives are shown in the following figure after the this._obverse() and this._compile() methods are executed in the myVue class

The source code

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="app">
    <div class="text" v-text="myText"></div>
    <input class="input" type="text" v-model="myText" >
</div>
</body>
<script>
    class myVue {
      constructor (options) {
        this.$el = document.querySelector(options.el)
        this.$data = options.data
        this._directives = {}
        this._obverse(this.$data)
        this._compile(this.$el)
      }
      _obverse (data) {
        let _dir
        let value
        // Iterate over the data object
        Object.entries(data).forEach(dataArray= > {
          this._directives[dataArray[0]] = [];
          value = dataArray[1]
          // The value exists and is an object
          if(value && value === 'object') {this._obverse(value)
          }
          _dir = this._directives[dataArray[0]]
          Object.defineProperty(this.$data, dataArray[0] and {enumerable: true.configurable: true.get: function() {
              return value
            },
            set: function(newValue) {
              // A simple identity comparison is performed without considering complex objects
              if(value ! == newValue){ value = newValue _dir.forEach(function(item){
                  item._update();
                })
              }
            }
          })
        })
      }
      _compile (el) {
        let nodes = el.children
        for(let i = 0; i < nodes.length; i ++){
          if(nodes[i].children.length){
            this._compile(nodes[i])
          }
          // Handle the V-text attribute on the DOM
          if(nodes[i].hasAttribute('v-text')) {let attrValue = nodes[i].getAttribute('v-text')
            this._directives[attrValue].push(new Watcher(nodes[i], this, attrValue, 'innerHTML'))}// Handle v-model attributes on input and textArea
          if(nodes[i].hasAttribute('v-model') && (nodes[i].tagName === 'INPUT' || nodes[i].tagName === 'TEXTAREA')) {let attrValue = nodes[i].getAttribute('v-model')
            this._directives[attrValue].push(new Watcher(nodes[i], this, attrValue, 'value'))
            nodes[i].addEventListener('input'.() = > {
              this.$data[attrValue] = nodes[i].value
            })
          }
        }
      }
    }
    class Watcher {
      constructor (el, vm, dataKey, elAttr) {
        // The DOM node to update
        this.el = el
        / / myVue instance
        this.vm = vm
        // The attribute name of data
        this.dataKey = dataKey
        // Dom node attribute value
        this.elAttr = elAttr
      }
      _update() {
        this.el[this.elAttr] = this.vm.$data[this.dataKey]
      }
    }
</script>
<script>
    const app = new myVue({
      el: '#app'.data: {
        myText: ' '}})</script>
</html>
Copy the code

conclusion

This article refers to the big guy’s blog for rewriting, interested readers can read through the “resources” jump, give the big old point like. This is the practical application of the observer pattern in JS projects. Through this example, we can further understand the significance of some API design in Vue, such as vue.set () method. As a React developer, I don’t have much experience in Vue. If there are bugs in this article, please kindly comment

The resources

Observer Patterns in JavaScript Design Patterns