A brief analysis of the basic implementation of Vue detection data changes

A brief analysis of the basic implementation of Vue detection data changes

1. Object change detection

Next, we will simulate the logic of detecting data changes.

Let me emphasize what we are going to do: notify the outside world of data changes (the outside world then does some of its own logical processing, such as re-rendering the view).

Before we start coding, we first need to answer the following questions:

1. How to detect changes in objects?

  • Use Object.defineProperty(). The getter is triggered when reading data, and the setter is triggered when modifying data.
  • Only by detecting changes in objects can notifications be issued when data changes.

2. Who do we notify when data changes?

  • Notify where the data is used. The data can be used in the template or in vm.$watch(). The behavior is different in different places, for example, the template needs to be rendered here, and other logic needs to be performed there. So just abstract out a class. When the data changes, notify it, and then it will notify other places.
  • This class is named Watcher. It's just an intermediary.

3. Who to depend on?

  • Whoever you notify depends on you, that is, Watcher.

4. When will you be notified?

  • When modifying data. That is, notification in setter

5. When to collect dependencies?

  • Because we need to inform the places where the data is used. To use data, we have to read the data. We can collect it when reading the data, that is, collect it in the getter.

6.Where is the data collected?

  • You can define an array in each attribute, and put all the dependencies related to the attribute in it.

The code is as follows (can be run directly):

// Global variables used to store dependencies let globalData = undefined;

// Convert data to responsive function defineReactive (obj, key, val) {
    // Dependency list let dependList = []
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function () {
        // Collect dependencies (Watcher)
        globalData && dependList.push(globalData)
        return val
      },
      set: function reactiveSetter (newVal) {
        if(val === newVal){
            return
        }
        // Notification dependencies (Watcher)
        dependList.forEach(w => {
            w.update(newVal, val)
        })
        val = newVal
      }
    });
}

// Depends on class Watcher{
    constructor(data, key, callback){
        this.data = data;
        this.key = key;
        this.callback = callback;
        this.val = this.get();
    }
    // This code can add itself to the dependency list get(){
        // Save dependencies in globalData
        globalData = this;
        // Collect dependencies when reading data let value = this.data[this.key]
        globalData = undefined
        return value;
    }
    // Receive notification when data changes, and then notify the outside world update(newVal, oldVal){
        this.callback(newVal, oldVal)
    }
}

/* The following is the test code */
let data = {};
// Make the name attribute responsive defineReactive(data, 'age', '88')
// When the data age changes, the Watcher will be notified, and then the Watcher will notify the outside world new Watcher(data, 'age', (newVal, oldVal) => {
    console.log(`Outside world: newVal = ${newVal}; oldVal = ${oldVal}`)
})

data.age -= 1 // Console output: External: newVal = 87; oldVal = 88

Continue to execute data.age -= 1 in the console, and外界:newVal = 86 ; oldVal = 87 .

Attached is a relationship diagram between Data, defineReactive, dependList, Watcher and the outside world.

First, convert data to responsive using the defineReactive() method ( defineReactive(data, 'age', '88') ).

The outside world reads data through Watcher ( let value = this.data[this.key] ), the data getter will be triggered, and then Watcher will be collected through globalData.

When the data is modified ( data.age -= 1 ), the setter will be triggered, the dependency (dependList) will be notified, the dependency will notify the Watcher ( w.update(newVal, val) ), and finally the Watcher will notify the outside world.

2. Questions about Object

Think about it: In the above example, will the outside world be notified if delete data.age is continued?

Won't. Because the setter will not be triggered. Please read on:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
    <div id='app'>
        <section>
            {{ p1.name }}
            {{ p1.age }}
        </section>
    </div>
<script>
const app = new Vue({
    el: '#app',
    data: {
        p1: {
            name: 'ph',
            age: 18
        }
    }
})
</script>
</body>
</html>

After running, the page will display ph 18 . We know that if the data is changed, the view will be re-rendered, so we execute delete app.p1.name in the console and find that the page has not changed. This is the same as executing delete data.age in the above example. It will not trigger the setter and will not notify the outside world.

To solve this problem, Vue provides two APIs (which will be introduced later): vm.$set and vm.$delete.

If you continue to execute app.$delete(app.p1, 'age') , you will find that the page has no information (the name attribute has been deleted with delete, but it was not re-rendered at that time).

Note : If app.p1.sex = 'man' is executed here, the place where the data p1 is used will not be notified. This problem can be solved by vm.$set.

Array Change Detection

3.1 Background

If let data = {a:1, b:[11, 22]} , after converting it to responsive type through Object.defineProperty, we modify the data data.a = 2 , which will notify the outside world. This is easy to understand. Similarly, data.b = [11, 22, 33] will also notify the outside world. However, if we modify data b in another way, like data.b.push(33) , it will not notify the outside world because the setter is not used. See the example:

function defineReactive(obj, key, val) {
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      get: function () {
        console.log(`get val = ${val}`)
        return val
      },
      set: function reactiveSetter (newVal) {
        if(val === newVal){
            return
        }
        console.log(`set val = ${newVal}; oldVal = ${val}`)
        val = newVal
      }
    });
}

// The following is the test code {1}
let data = {}
defineReactive(data, 'a', [11,22])
data.a.push(33) // get val = 11,22 (setter not triggered) {2}     
data.a // get val = 11,22,33 
data.a = 1 // set val = 1; oldVal = 11,22,33 (triggering setter)

Changing the value of the array through the push() method does not trigger the setter (line {2}), so the outside world cannot be notified. This seems to illustrate a problem: through the Object.definePropery() method, only objects can be converted to responsiveness, but arrays cannot be converted to responsiveness.

In fact, Object.definePropery() can convert arrays into responsive ones. See the example:

// Continuing with the above example, change the test code (line {1}) to:
let data = []
defineReactive(data, '0', 11)
data[0] = 22 // set val = 22; oldVal = 11
data.push(33) // will not trigger {10}

Although Object.definePropery() can make an array responsive, modifying the array via data.push(33) (line {10}) will still not notify the outside world.

Therefore, in Vue, two sets of methods are used to convert data into responsiveness: objects use Object.defineProperty(); arrays use another set.

3.2 Implementation

In es6, Proxy can be used to detect changes in arrays. See the example:

let data = [11,22]
let p = new Proxy(data, {
    set: function(target, prop, value, receiver) {
        target[prop] = value;
        console.log('property set: ' + prop + ' = ' + value);
        return true;
    }
    })
console.log(p)
p.push(33)
/*
Output:
[ 11 , 22 ]
property set: 2 = 33
property set: length = 3
*/

It was a little more troublesome before es6, and you could use interceptors. The principle is: when we execute [].push() , the method in the array prototype (Array.prototype) will be called. We add an interceptor between [].push() and Array.prototype When [].push() is called in the future, the push() method in the interceptor is executed first, and the push() method in the interceptor calls the push() method in Array.prototype. See the example:

// Array prototype let arrayPrototype = Array.prototype

// Create an interceptor let interceptor = Object.create(arrayPrototype)

// Associate the interceptor with the original array's methods; ('push,pop,unshift,shift,splice,sort,reverse').split(',')
.forEach(method => {
    let origin = arrayPrototype[method];
    Object.defineProperty(interceptor, method, {
        value: function(...args){
            console.log(`interceptor: args = ${args}`)
            return origin.apply(this, args);
        },
        enumerable: false,
        writable: true,
        configurable: true
    })
});

// Test let arr1 = ['a']
let arr2 = [10]
arr1.push('b')
// Detect changes in array arr2 Object.setPrototypeOf(arr2, interceptor) // {20}
arr2.push(11) // interceptor: args = 11
arr2.unshift(22) // interceptor: args = 22

This example adds seven methods that can change the contents of the array itself to the interceptor. If you need to detect changes to an array, point the prototype of the array to the interceptor (line {20}). When we modify the array through 7 methods such as push, it will be triggered in the interceptor, so that the outside world can be notified.

At this point, we have only completed the task of detecting array changes.

When data changes, notify the outside world. The above encoding implementation is only for Object data, but here we need to implement it for Array data.

Let’s think about the same question:

1. How to detect changes in an array?

  • Interceptor

2. Who do we notify when data changes?

  • Watcher

3. Who to depend on?

  • Watcher

4. When will you be notified?

  • When modifying data. Notification in the interceptor.

5. When to collect dependencies?

  • Because we need to inform the places where the data is used. To use data, you have to read data. Collected when reading data. This is the same as object collection dependencies.
  • {a: [11,22]} For example, if we want to use array a, we must access the attribute a of the object.

6.Where is the data collected?

  • The object collects dependencies in each attribute, but here we have to consider that the array can trigger dependencies in the interceptor, and the position may need to be adjusted

That’s all, I won’t go into details here. In the next article, I will extract the source code related to data detection in Vue and analyze it briefly in conjunction with this article.

IV. Questions about Array

// You need to import vue.js yourself. In the future, we will try to list only the core code <div id='app'>
        <section>
            {{ p1[0] }}
            {{ p1[1] }}
        </section>
</div>
<script>
const app = new Vue({
    el: '#app',
    data: {
        p1: ['ph', '18']
    }
})
</script>

After running, ph 18 is displayed on the page. The console executes app.p1[0] = 'lj' and the page does not respond, because the array can only notify the outside world through the interceptor by calling the specified 7 methods. If you execute app.$set(app.p1, 0, 'pm') the page content will become pm 18 .

The above is a brief analysis of the basic implementation of vue detection data changes. For more information about vue detection data changes, please pay attention to other related articles on 123WORDPRESS.COM!

You may also be interested in:
  • Vue implements 3 ways to switch tabs and switch to maintain data status
  • Analysis and solution of data loss during Vue component value transfer
  • Implementation of large-screen display of data visualization based on vue+echarts
  • Implementation of drag data visualization function in Vue based on Echarts
  • Vue gets the data but cannot render it on the page
  • Antd-vue Table component adds Click event to click on a row of data tutorial
  • vue+echarts+datav large screen data display and implementation of China map province, city and county drill-down function
  • Pitfalls encountered in vuex, vuex data changes, page rendering operations in components
  • Vue implements data sharing and modification operations between two components

<<:  Installation process of CentOS8 Linux 8.0.1905 (illustration)

>>:  Detailed explanation of galera-cluster deployment in cluster mode of MySQL

Recommend

Detailed explanation of HTML onfocus gain focus and onblur lose focus events

HTML onfocus Event Attributes Definition and Usag...

K3s Getting Started Guide - Detailed Tutorial on Running K3s in Docker

What is k3d? k3d is a small program for running a...

Using js to implement a number guessing game

Last week, the teacher gave me a small homework, ...

Simple implementation method of vue3 source code analysis

Table of contents Preface 🍹Preparation 🍲vue3 usag...

Full process record of Nginx reverse proxy configuration

1. Preparation Install Tomcat on Linux system, us...

How to Monitor Linux Memory Usage Using Bash Script

Preface There are many open source monitoring too...

React Router 5.1.0 uses useHistory to implement page jump navigation

Table of contents 1. Use the withRouter component...

Summary of Several Methods for Implementing Vertical Centering with CSS

In the front-end layout process, it is relatively...

Linux CentOS6.9 installation graphic tutorial under VMware

As a technical novice, I am recording the process...

Example of using CSS3 to create Pikachu animated wallpaper

text OK, next it’s time to show the renderings. O...

Shell script settings to prevent brute force ssh

The shell script sets access control, and the IP ...

How to install and configure the supervisor daemon under centos7

Newbie, record it yourself 1. Install supervisor....

Example of how to implement keepalived+nginx high availability

1. Introduction to keepalived Keepalived was orig...