Vue data two-way binding implementation method

Vue data two-way binding implementation method

1. Introduction

This article is suitable for beginners who are learning Vue source code. After reading it, you will have a general understanding of the principle of two-way data binding in Vue, and understand the three major roles of Observer, Compile, and Wathcer (as shown in the figure below) and their functions.

This article will take you step by step to implement a simple version of two-way data binding. Each step will analyze in detail the problem to be solved in this step and why the code is written in this way. Therefore, after reading this article, I hope you can implement a simple version of two-way data binding by yourself.

2. Code Implementation

2.1 Purpose Analysis

The effect to be achieved in this article is shown in the following figure:

The HTML and JS main codes used in this article are as follows:

<div id="app">
  <h1 v-text="msg"></h1>
  <input type="text" v-model="msg">
  <div>
    <h1 v-text="msg2"></h1>
    <input type="text" v-model="msg2">
  </div>
</div>
let vm = new Vue({
    el: "#app",
    data: {
      msg: "hello world",
      msg2: "hello xiaofei"
    }
  })

We will do this in three steps:

  • Step 1: Synchronize the data in data to the page to achieve the initialization of M ==> V;
  • Step 2: When a value is entered in the input box, the new value is synchronized to the data to achieve the binding of V ==> M;
  • Step 3: When the data is updated, the page is triggered to change, realizing the binding of M ==> V.

2.2 Implementation process

2.2.1 Entry code

First, we need to create a Vue class that receives an options object. At the same time, we need to save the valid information in the options object.

Then, we have three main modules: Observer, Compile, and Wathcer. Among them, Observer is used for data hijacking, Compile is used to parse elements, and Wathcer is an observer. You can write the following code: (There is no need to study the three concepts of Observer, Compile, and Wathcer in detail, as they will be explained in detail later).

class Vue {
    // Receive the passed object constructor(options) {
      // Save valid information this.$el = document.querySelector(options.el);
      this.$data = options.data;

      //Container: {attribute 1: [wathcer1, wathcer2...], attribute 2: [...]}, used to store each attribute observer this.$watcher = {};

      // Parsing elements: Implementing Compile
      this.compile(this.$el); // To parse the element, you have to pass the element in // Hijacking data: Implementing Observer
      this.observe(this.$data); // To hijack data, you have to pass the data in}
    compile() {}
    observe() {}
  }

2.2.2 Page Initialization

In this step, we need to initialize the page, that is, parse the v-text and v-model instructions, and render the data in data to the page.

The key to this step is to implement the compile method, so how to parse the el element? The idea is as follows:

  • First, we need to get all the child nodes under el, and then traverse these child nodes. If the child nodes have child nodes, we need to use the idea of ​​recursion;
  • Traverse the child nodes to find all elements with instructions, and render the corresponding data into the page.

The code is as follows: (mainly look at the compile part)

class Vue {
    // Receive the passed object constructor(options) {
      // Get useful information this.$el = document.querySelector(options.el);
      this.$data = options.data;

      // Container: {attribute1: [wathcer1, wathcer2...], attribute2: [...]}
      this.$watcher = {};

      // 2. Parsing elements: Implementing Compile
      this.compile(this.$el); // To parse the element, you have to pass the element in // 3. Hijacking data: Implementing Observer
      this.observe(this.$data); // To hijack data, you have to pass the data in}
    compile(el) {
      // Parse each child node under the element, so get el.children
      // Note: children returns an element set, childNodes returns a node set let nodes = el.children;

      // Parse the instructions for each child node for (var i = 0, length = nodes.length; i < length; i++) {
        let node = nodes[i];
        // If the current node has child elements, recursively parse the node if(node.children){
          this.compile(node);
        }
        // Parse elements with v-text directive if (node.hasAttribute("v-text")) {
          let attrVal = node.getAttribute("v-text");
          node.textContent = this.$data[attrVal]; // Render the page}
        // Parse elements with v-model directives if (node.hasAttribute("v-model")) {
          let attrVal = node.getAttribute("v-model");
          node.value = this.$data[attrVal];
        }
      }
    }
    observe(data) {}
  }

In this way, we have achieved the initialization of the page.

2.2.3 Views affect data

Because input has a v-model instruction, we need to implement such a function: when characters are entered in the input box, the data bound in data changes accordingly.

We can bind an input event to the input element. The effect of the event is to modify the corresponding data in data to the value in input.

The implementation code of this part is relatively simple. You can understand it by looking at the marked place. The code is as follows:

class Vue {
    constructor(options) {
      this.$el = document.querySelector(options.el);
      this.$data = options.data;
      
      this.$watcher = {};  

      this.compile(this.$el);

      this.observe(this.$data);
    }
    compile(el) {
      let nodes = el.children;

      for (var i = 0, length = nodes.length; i < length; i++) {
        let node = nodes[i];
        if(node.children){
          this.compile(node);
        }
        if (node.hasAttribute("v-text")) {
          let attrVal = node.getAttribute("v-text");
          node.textContent = this.$data[attrVal];
        }
        if (node.hasAttribute("v-model")) {
          let attrVal = node.getAttribute("v-model");
          node.value = this.$data[attrVal];
          // Look here! ! Only three more lines of code! !
          node.addEventListener("input", (ev)=>{
            this.$data[attrVal] = ev.target.value;
            // You can try to execute here: console.log(this.$data),
            // You can see that every time you enter text in the input box, the msg value in data also changes})
        }
      }
    }
    observe(data) {}
  }

2.2.4 Data Impact View

So far, we have achieved that when we enter characters in the input box, the data in data will be automatically updated;

The main task of this section is: when the data in data is updated, the elements bound to the data will automatically update the view on the page. The specific ideas are as follows:

1) We are going to implement a Watcher class, which has an update method to update the page. The observer code is as follows:

class Watcher{
    constructor(node, updatedAttr, vm, expression){
      //Save the passed values. These values ​​are used when rendering the page. this.node = node;
      this.updatedAttr = updatedAttr;
      this.vm = vm;
      this.expression = expression;
      this.update();
    }
    update(){
      this.node[this.updatedAttr] = this.vm.$data[this.expression];
    }
  }

2) Think about it, to which data should we add observers? When to add observers to data?

When parsing the element, when the v-text and v-model instructions are parsed, it means that this element needs to be bound to the data in both directions, so we add an observer to the container at this time. We need to use a data structure like this: {attribute 1: [wathcer1, wathcer2...], attribute 2: [...]}. If it is not very clear, you can see the following figure:

You can see that there is a $watcher object in the vue instance. Each attribute of $watcher corresponds to each data that needs to be bound, and the value is an array used to store the observers who have observed the data. (Note: Vue source code specifically creates a class called Dep, which corresponds to the array mentioned here. This article is a simplified version, so I won’t introduce it in detail.)

3) Hijacking data: Use the object's accessor property getter and setter to trigger an action when the data is updated. The main purpose of this action is to allow all observers who have observed the data to execute the update method.

To summarize, what we need to do in this section:

  1. Implement a Watcher class;
  2. Add observers when parsing instructions (i.e. in the compile method);
  3. Implement data hijacking (implement the observe method).

The complete code is as follows:

  class Vue {
    // Receive the passed object constructor(options) {
      // Get useful information this.$el = document.querySelector(options.el);
      this.$data = options.data;

      // Container: {attribute1: [wathcer1, wathcer2...], attribute2: [...]}
      this.$watcher = {};

      // Parsing elements: Implementing Compile
      this.compile(this.$el); // To parse the element, you have to pass the element in // Hijacking data: Implementing Observer
      this.observe(this.$data); // To hijack data, you have to pass the data in}
    compile(el) {
      // Parse each child node under the element, so get el.children
      // Extension: children returns an element set, childNodes returns a node set let nodes = el.children;

      // Parse the instructions for each child node for (var i = 0, length = nodes.length; i < length; i++) {
        let node = nodes[i];
        // If the current node has child elements, recursively parse the node if (node.children) {
          this.compile(node);
        }
        if (node.hasAttribute("v-text")) {
          let attrVal = node.getAttribute("v-text");
          // node.textContent = this.$data[attrVal]; 
          // Watcher calls update when instantiated, replacing this line of code/**
           * Imagine what data Wathcer needs to use when updating node data? 
           * egpinnerHTML = vm.$data[msg]
           * So the parameters to be passed in are: current node, node attributes to be updated, vue instance, bound data attributes*/
          // Add observers to the container: {msg1: [Watcher, Watcher...], msg2: [...]}
          if (!this.$watcher[attrVal]) {
            this.$watcher[attrVal] = [];
          }
          this.$watcher[attrVal].push(new Watcher(node, "innerHTML", this, attrVal))
        }
        if (node.hasAttribute("v-model")) {
          let attrVal = node.getAttribute("v-model");
          node.value = this.$data[attrVal];

          node.addEventListener("input", (ev) => {
            this.$data[attrVal] = ev.target.value;
          })

          if (!this.$watcher[attrVal]) {
            this.$watcher[attrVal] = [];
          }
          // Different from the innerHTML used above, the input here uses the value attribute this.$watcher[attrVal].push(new Watcher(node, "value", this, attrVal))
        }
      }
    }
    observe(data) {
      Object.keys(data).forEach((key) => {
        let val = data[key]; // This val will always be stored in memory. Every time you access data[key], you are accessing this val
        Object.defineProperty(data, key, {
          get() {
            return val; // You cannot return data[key] directly here, otherwise it will fall into an infinite loop},
          set(newVal) {
            if (val !== newVal) {
              val = newVal; // Similarly, data[key] cannot be set directly here, which will lead to an infinite loop this.$watcher[key].forEach((w) => {
                w.update();
              })
            }
          }
        })
      })
    }
  }

  class Watcher {
    constructor(node, updatedAttr, vm, expression) {
      //Save the passed value this.node = node;
      this.updatedAttr = updatedAttr;
      this.vm = vm;
      this.expression = expression;
      this.update();
    }
    update() {
      this.node[this.updatedAttr] = this.vm.$data[this.expression];
    }
  }

  let vm = new Vue({
    el: "#app",
    data: {
      msg: "hello world",
      msg2: "hello xiaofei"
    }
  })

At this point, the code is complete.

3. Future plans

Use the knowledge of design patterns to analyze the problems in the source code above and compare it with the Vue source code, which is considered an analysis of the Vue source code.

The above is the detailed content of the implementation method of Vue data two-way binding. For more information about Vue data two-way binding, please pay attention to other related articles on 123WORDPRESS.COM!

You may also be interested in:
  • Detailed explanation of the implementation principle of Vue2.0/3.0 two-way data binding
  • Analysis on the problem of data loss caused by forced refresh of vuex
  • How does Vue track data changes?
  • Vue+canvas realizes the effect of refreshing waterfall chart from top to bottom in real time (similar to QT)
  • Solution to Vue data assignment problem
  • Vue resets data to its initial state
  • Analysis and solution of data loss during Vue component value transfer
  • SpringBoot+Vue realizes data adding function
  • Handwritten Vue2.0 data hijacking example
  • Avoid abusing this to read data in data in Vue
  • Design a data collector with vue

<<:  Detailed explanation of selinux basic configuration tutorial in Linux

>>:  Example of how to create a local user in mysql and grant database permissions

Recommend

JS+AJAX realizes the linkage of province, city and district drop-down lists

This article shares the specific code of JS+AJAX ...

Detailed explanation of the JavaScript timer principle

Table of contents 1. setTimeout() timer 2. Stop t...

Cleverly use CSS3's webkit-box-reflect to achieve various dynamic effects

In an article a long time ago, I talked about the...

Some points on using standard HTML codes in web page creation

<br />The most common mistake made by many w...

Detailed explanation of Nginx Rewrite usage scenarios and code examples

Nginx Rewrite usage scenarios 1. URL address jump...

Several scenarios for using the Nginx Rewrite module

Application scenario 1: Domain name-based redirec...

How to connect Navicat to the docker database on the server

Start the mysql container in docekr Use command: ...

A brief analysis of controlled and uncontrolled components in React

Table of contents Uncontrolled components Control...

Zookeeper stand-alone environment and cluster environment construction

1. Single machine environment construction# 1.1 D...

Vue+video.js implements video playlist

This article shares the specific code of vue+vide...

Building a KVM virtualization platform on CentOS7 (three ways)

KVM stands for Kernel-based Virtual Machine, whic...

How to implement multiple parameters in el-dropdown in ElementUI

Recently, due to the increase in buttons in the b...