Let’s talk about the understanding of two-way binding in Vue

Two-way binding in Vue is a mechanism that implements dynamic interaction between Model and View. When either the Model or the View changes, the other is updated accordingly. This is achieved through data hijacking and the publish-subscribe model.

Illustration:

Insert image description here

1. What is two-way binding?

Let's start with one-way binding. It's very simple to bind the Model to the View. When we update the Model with JavaScript code, the View will automatically update. It's easy to think of two-way binding. In one-way binding On a certain basis, when the user updates the View, the Model data is automatically updated. This situation is two-way binding. For example

Insert image description here
When the user fills in the form, the status of the View is updated. If the status of the Model can be automatically updated at this time, it is equivalent to us making a two-way binding relationship between the Model and the View. The diagram is as follows

Insert image description here

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.

  1. Data layer (Model): application data and business logic
  2. View layer (View): application display effect, various UI components
  3. 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: the core function of the control layer in MVVM 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:

  1. Update view after data changes
  2. Update data after view changes

Of course, it also consists of two main parts

Observer : monitors the attributes of all data.
Compiler : scans and parses the instructions of each element node, replaces the 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 the data , this process occurs in Compile
  3. Define an update function and Watcher at the same time. Watcher will call the update function when the corresponding data changes in the future.
  4. Since a certain key of data may appear multiple times in a view, each key requires a housekeeper Dep to manage multiple Watchers .
  5. Once the data in the data changes in the future , the corresponding Dep will be found first and all Watchers will be notified to execute the update function.

The flow chart is as follows:

Insert image description here

accomplish

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;  
        
    // 对data选项做响应式处理  
    observe(this.$data);  
        
    // 代理data到vm上  
    proxy(this);  
        
    // 执行编译  
    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

Insert image description here

class Compile {  
  constructor(el, vm) {  
    this.$vm = vm;  
    this.$el = document.querySelector(el);  // 获取dom  
    if (this.$el) {  
      this.compile(this.$el);  
    }  
  }  
  compile(el) {  
    const childNodes = el.childNodes;   
    Array.from(childNodes).forEach((node) => { // 遍历子元素  
      if (this.isElement(node)) {   // 判断是否为节点  
        console.log("编译元素" + node.nodeName);  
      } else if (this.isInterpolation(node)) {  
        console.log("编译插值⽂本" + node.textContent);  // 判断是否为插值文本 {
   
   {}}  
      }  
      if (node.childNodes && node.childNodes.length > 0) {  // 判断是否有子元素  
        this.compile(node);  // 对子元素进行递归遍历  
      }  
    });  
  }  
  isElement(node) {  
    return node.nodeType == 1;  
  }  
  isInterpolation(node) {  
    return node.nodeType == 3 && /\{\{(.*)\}\}/.test(node.textContent);  
  }  
}  

dependency collection

A certain key in the data will be used in the view , which is called a dependency . The same key may appear multiple times, and each time it needs to be collected and maintained by a Watcher . This process is called dependent collection. Multiple Watchers require a Dep to manage them. When updates are needed, the Dep will notify them uniformly .

Insert image description here

Implementation ideas

  1. Create a Dep instance for each key when definingReactive
  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 the setter is triggered, it can be notified through the corresponding Dep to manage all Watcher updates .
// 负责更新视图  
class Watcher {
    
      
  constructor(vm, key, updater) {
    
      
    this.vm = vm  
    this.key = key  
    this.updaterFn = updater  
  
    // 创建实例时,把当前实例指定到Dep.target静态属性上  
    Dep.target = this  
    // 读一下key,触发get  
    vm[key]  
    // 置空  
    Dep.target = null  
  }  
  
  // 未来执行dom更新函数,由dep调用的  
  update() {
    
      
    this.updaterFn.call(this.vm, this.vm[this.key])  
  }  
}  

DeclarationDep _

class Dep {
    
      
  constructor() {
    
      
    this.deps = [];  // 依赖管理  
  }  
  addDep(dep) {
    
      
    this.deps.push(dep);  
  }  
  notify() {
    
       
    this.deps.forEach((dep) => dep.update());  
  }  
}  

Getter triggered 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 && dep.addDep(Dep.target);// Dep.target也就是Watcher实例  
      return val;  
    },  
    set(newVal) {
    
      
      if (newVal === val) return;  
      dep.notify(); // 通知dep执行更新方法  
    },  
  });  
}  

In general, Vue's two-way binding mechanism makes it easier for developers to handle the interaction between UI and business logic, reducing code redundancy and maintenance difficulty.

Guess you like

Origin blog.csdn.net/He_9a9/article/details/132846209