Revealing the MVVM architecture under the Vue framework: the technical details behind two-way data binding

Jiangcheng Cheerful Pea: Personal homepage

Personal column :《VUE》《javaScript》

Personal website : “Jiangcheng Cheerful Pea”

The ideal of life is for an ideal life!

Table of Contents

? Column introduction

1. What is two-way binding?

2. What is the principle of two-way binding?

Understanding ViewModel

3. Implement two-way binding

accomplish

CompileCompile

dependency collection

?Write at the end


? Column Introduction

Welcome to the front-end entry journey! This column is created for readers who are interested in web development and have just started learning front-end. Whether you are a beginner or a developer with some basic knowledge, we will provide you with a systematic and friendly learning platform here. We update it in the form of questions and answers to present you with selected front-end knowledge points and best practices. By explaining concepts in simple and easy-to-understand terms and providing practical examples and exercises, you can gradually build a solid foundation. Whether it is HTML, CSS, JavaScript or the latest front-end frameworks and tools, we will provide you with rich content and practical skills to help you better understand and apply various technologies in front-end development.

At the same time, we will also pay attention to the latest front-end trends and developments. As Web technology continues to evolve, front-end development is also constantly innovating. We will introduce the latest front-end frameworks, tools and technologies in a timely manner so that you can stay at the forefront and keep pace with the times. By mastering the latest front-end technologies, you will be able to become more competitive in the highly competitive field of web development.

1. What is two-way binding

Let’s first switch from one-way binding to one-way binding. It is very simple. It is to bind Model to View. When we use JavaScript code to update Model, View will automatically update the two-way binding. It is easy to think of it. On the basis of one-way binding, the user updates the View, the data of Model is also automatically updated. This is an example of two-way binding.

When the user fills in the form, the status of View is updated. If the status of Model can be automatically updated at this time, it is equivalent to us putting Model and View is as follows:

2. What is the principle of two-way binding

We all know that Vue is a framework for two-way data binding. Two-way binding consists of three important parts.

  • Data layer (Model): application data and business logic
  • View layer (View): application display effect, various UI components
  • Business logic layer (ViewModel): the core of the framework encapsulation, which is responsible for associating data with views

The above layered architecture solution can be called a professional term: MVVM The core function of the control layer here is "two-way data binding". Naturally, we only need to understand what it is to further understand the principles of data binding

Understanding ViewModel

Its main responsibilities are:

  • Update view after data changes
  • Update data after view changes

Of course, it also consists of two main parts

  • Observer: monitors the properties of all data
  • Compiler: scans and parses the instructions of each element node, replaces data according to the instruction template, and binds the corresponding update function

3. Implement two-way binding

Let’s take Vue as an example. Let’s first take a look at the two-way binding process in Vue.

  1. new Vue() first performs initialization and performs responsive processing on data. This process occurs in Observe
  2. At the same time, compile the template, find the dynamically bound data, obtain and initialize the view from data, this process occurs in Compile
  3. Define an update function and Watcher at the same time. When the corresponding data changes in the future, Watcher will call the update function.
  4. Since a key of data may appear multiple times in each view, each key requires a housekeeper Dep To manage multiple Watchers
  5. Once the data in data changes in the future, the corresponding Dep will be found first, and all Watcher will be notified to execute the update function.

The flow chart is as follows:

implementation

Let’s start with a constructor: perform initialization and perform responsive processing on data

class Vue {
  constructor(options) {
    this.$options = options;
    this.$data = options.data;
        
    //Responsive processing of data options
    observe(this.$data);
        
    // Agent data to vm
    proxy(this);
        
    //Execute compilation
    new Compile(options.el, this);
  }
} 

Perform responsive specific operations on the data option

function observe(obj) {
  if (typeof obj !== "object" || obj == null) {
    return;
  }
  new Observer(obj);
}
  
class Observer {
  constructor(value) {
    this.value = value;
    this.walk(value);
  }
  walk(obj) {
    Object.keys(obj).forEach((key) => {
      defineReactive(obj, key, obj[key]);
    });
  }
} 
CompileCompile

Scan and parse the instructions of each element node, replace the data according to the instruction template, and bind the corresponding update function

class Compile {
  constructor(el, vm) {
    this.$vm = vm;
    this.$el = document.querySelector(el); // Get dom
    if (this.$el) {
      this.compile(this.$el);
    }
  }
  compile(el) {
    const childNodes = el.childNodes;
    Array.from(childNodes).forEach((node) => { // Traverse child elements
      if (this.isElement(node)) { // Determine whether it is a node
        console.log("Compile element" + node.nodeName);
      } else if (this.isInterpolation(node)) {
        console.log("Compile interpolation? This" + node.textContent); // Determine whether it is interpolated text {<!-- -->{}}
      }
      if (node.childNodes & amp; & amp; node.childNodes.length > 0) { // Determine whether there are child elements
        this.compile(node); // Recursively traverse child elements
      }
    });
  }
  isElement(node) {
    return node.nodeType == 1;
  }
  isInterpolation(node) {
    return node.nodeType == 3 & amp; & amp; /\{\{(.*)\}\}/.test(node.textContent);
  }
}
  
Dependency Collection

A certain key in data will be used in the view, which is called dependency. The same key may appear multiple times, and each time it needs to be collected and maintained using a Watcher. This process is called dependent collection of multiple Watcher requires a Dep to manage, and will be notified by the Dep system when it needs to be updated.

Implementation ideas

  1. defineReactive creates one Dep instance for each key
  2. When initializing the view, read a key, such as name1, and create a watcher1
  3. Since the getter method of name1 is triggered, watcher1 is added to the Dep corresponding to name1
  4. When name1 is updated and setter is triggered, it can be notified through the corresponding Dep to manage all Watcher updates
//Responsible for updating the view
class Watcher {
  constructor(vm, key, updater) {
    this.vm = vm
    this.key = key
    this.updaterFn = updater
  
    // When creating an instance, assign the current instance to the Dep.target static property
    Dep.target = this
    // Read the key and trigger get
    vm[key]
    // Blanking  
    Dep.target = null
  }
  
  //The dom update function will be executed in the future, called by dep
  update() {
    this.updaterFn.call(this.vm, this.vm[this.key])
  }
} 

DeclarationDep

class Dep {
  constructor() {
    this.deps = []; // Dependency management
  }
  addDep(dep) {
    this.deps.push(dep);
  }
  notify() {
    this.deps.forEach((dep) => dep.update());
  }
} 

Triggers getter when creating watcher

class Watcher {
  constructor(vm, key, updateFn) {
    Dep.target = this;
    this.vm[this.key];
    Dep.target = null;
  }
}
  

Dependency collection, create Dep instance

function defineReactive(obj, key, val) {
  this.observe(val);
  const dep = new Dep();
  Object.defineProperty(obj, key, {
    get() {
      Dep.target & amp; & amp; dep.addDep(Dep.target);// Dep.target is the Watcher instance
      return val;
    },
    set(newVal) {
      if (newVal === val) return;
      dep.notify(); // Notify dep to execute the update method
    },
  });
} 

? Write at the end

Please feel free to give me some advice, comment below or send me a private message. Thank you very much.

? I think a certain part of my design is too cumbersome. Is there a simpler or higher-quality packaging method?

? Think some of my code is too old and can provide new API or the latest syntax?

? Don’t understand some of the content in the article

?Answer some questions in my article

? Think that some interactions and functions need to be optimized, and find bugs

? Want to add new features and have better suggestions for the overall design and appearance?

Finally, thank you for your patience in watching. Now that we are here, click Like and go!

syntaxbug.com © 2021 All Rights Reserved.