🗝 ️ preface

This is the 27th day of my participation in Gwen Challenge

Recently, after a certain stage of learning VUE, I was wondering if I could build something. So my friends suggested that I could start by looking at the source code of Vue. I had no clue and was afraid to take this step. Finally, opened my source code learning road.

So I searched for some common principles to learn, MY first step to vUE source code from vUE responsive principles.

In the following article, I will record the summary of learning the vUE responsive principle. Let’s find out ~🙋

1. Componentization basis

1. Componentization of “a long time ago”

(1) ASP JSP PHP era

A long time ago, probably the first programmers to touch web development, there was already componentization in their generation.

(2) nodejs

Nodejs is a latecomer to ASP, JSP, and PHP, but similar componentization is available in NodeJS, such as ejS, a template engine for JS.

Let’s take a look at how EJS implements ✍ componentization:

<! - personal information - > < div class = "right - item" > < % - the include (' widgets/user - info, {the userInfo: userData. The userInfo, isMe:userData.isMe, amIFollowed:userData.amIFollowed, atCount:userData.atCount }); %> </div>Copy the code
<! --> <% -include ('widgets/user-list', {count: userdata.count, userList: userdata.list}); % >Copy the code

As you can see from the above code, EJS implements data rendering by defining a component in the form of <%- %>.

There were components in the early days, but for traditional components, they were statically rendered, and their updates depended on manipulating the DOM. In this case, the difference between component development and component development is not that great.

So, to solve this problem, there are now popular vue and React, based on data-driven view development.

2. Data-driven View (MVVM, setState)

(1) Data-driven view – Vue MVVM

The componentization definition of vUE is as follows:

<template>
	<div id="app">
        <img
        	alt="Vue logo"
          	src="./assets/logo.png"
        >
        <HelloWorld
        msg="Welcome to your Vue.js App"
        />
    </div>
</template>
Copy the code

Using the official image, let’s talk about the MVVM of Vue.

The MVVM stands for model-view-viewModel.

A View is a View, which is the DOM.

A Model is a Model, which can be understood as data in a Vue component.

So between these two, we’re going to do it through the ViewModel. The ViewModel can do a lot of things, such as listening events, listening instructions and so on. When the data in the Model layer is modified, the ViewModel can be used to render the data to the View View layer. Conversely, when the View layer fires DOM events, the ViewModel can be used to enable the Model layer to modify the data.

This is the data-driven View in Vue, which modifies the data in the Model layer to drive it into the View.


With the basic concepts behind it, let’s use a piece of code to parse what MVVM looks like in Vue.

<template>
	<div id="app">
        <p @click="changeName">{{name}}</p>
        <ul>
            <li v-for="(item, index) in list" :key="index">
                {{item}}
            </li>
        </ul>
        <button @click="addItem">To add a</button>
    </div>
</template>
<script>
export default {
    name:'app'.data(){
        return{
            name:'vue'.list: ['a'.'b'.'c']}},methods: {changeName(){
            this.name = 'monday';
        },
        addItem(){
            this.list.push(`The ${Date.now()}`); }}}</script>
Copy the code

In the code above, the Template section represents the View layer, and the data below represents the Model layer. After that, click events like @click trigger specific methods, which can be regarded as the ViewModel layer. In this case, it can be understood that the ViewModel layer is a bridge connecting the View layer and the Model layer.

(2) React setState

React componentization is defined as follows:

function App(){
    return(
    	<div className="App">
            <header className="AppHeader">
            	<img
                    src={logo}
                    className="App-logo"
                    alt="logo"
                    />
                <HelloWorld
                    msg="Welcome to Your React App"
                    />
            </header>
        </div>
    );
}
Copy the code

React uses setState to manipulate data-driven views. The react data – driven view will not be discussed in detail. You can query the data based on your own requirements

(3) Summary

Vue and React help us render views through data, which allows us to focus more on business logic when developing VUE and React, rather than on DOM updates as traditional components do.

Two, Vue response

1. What is the response of VUE

The responsiveness of a VUE is that changes in the component data immediately trigger an update to the view. The first step in implementing data-driven views is to understand one of the core apis for implementing responsiveness, object.defineProperty.

Object. DefineProperty

We use code to demonstrate the use of Object.defineProperty as follows:

const data = {}
const name = 'friday'
Object.defineProperty(data, "name", {
    get:function () {
        console.log('get')
        return name
    },
    set: function (newVal) {
        console.log('set')
        name = newVal
    }
})

/ / test
console.log(data.name) //get friday
data.name = 'monday' //set
Copy the code

As can be seen from the above code, through Object.defineProperty, we can implement get and set operations on data, that is, get data and modify data, so as to achieve responsive monitoring of data.

How does Object.defineProperty implement responsiveness? Find out below!

3, oject.defineProperty implementation reactive

(1) Listening object

In order to understand reactive, you need to have an understanding of JS data types and deep copy. I wrote an article here before, if you need to check it out

We all know that JS data types have basic data types and reference data types. Next we will implement reactive listening for these two data types.

Basic data types:

// Trigger to update the view
function updateView() {
    console.log('View Update')}// Redefine attributes to listen
function defineReactive(target, key, value) {
    // Deep monitor
    observer(value)

    / / core API
    Object.defineProperty(target, key, {
        get() {
            return value
        },
        set(newValue) {
            if(newValue ! == value) {// Deep monitor
                observer(newValue)

                // Set the new value
                // Note that value is always in the closure, and once it is set, the next get will get the latest value
                value = newValue

                // Trigger to update the view
                updateView()
            }
        }
    })
}

// Listen for object properties
function observer(target) {
    // Determine the basic data type or reference data type
    if (typeoftarget ! = ='object' || target === null) {
        // Not objects or arrays
        return target
    }

    // Redefine attributes (for in can also iterate over arrays)
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
}

Copy the code
// Prepare data
const data = {
    name: 'monday'.age: 20
}

// Listen for data
observer(data)

/ / test
data.name = 'lisi'
data.age = 18
console.log('name', data.name)
console.log('age', data.age)
Copy the code

The console prints as follows:

As you can see from the figure above, we change the values of two data sets, and the data is updated in real time. In the console, we can see that the values of two data are changed and two “view updates” are displayed, indicating that the two data listening is successful.

Reading the code, we can see that when we are listening for a primitive data type, the value of target is returned directly and the view is updated in real time.

Also, note that object.defineProperty () does not listen to data when adding or deleting attributes.

What does that mean? So let’s do that.

Based on the above code, we add the following two lines.

data.x = '100' // Add new attribute, can't listen -- use Vue. Set
delete data.name // Delete attributes, no listening -- use vue.delete
Copy the code

The console output is as follows:

As you can see, adding these two lines of code has the same effect as before. Therefore, we can conclude that when attributes are added or deleted with Object.defineProperty(), the data is not heard. Even if the data is modified, the view cannot listen for the corresponding data, so there is no way to update the view.


Reference data types:

Again, based on the first section of the basic data type, we listen for data that references the data type. The test code is as follows:

// Prepare data
const data = {
    name: 'monday'.age: 20.info: {
        address: 'the fuzhou' // Deep listening is required
    },
    nums: ['Play basketball'.'Come out and play'.'Play table tennis']}// Listen for data
observer(data)

/ / test
data.info.address = 'Shanghai' // Deep monitor
data.nums.push('神游') // Listen for arrays
Copy the code

The following information is displayed:

We can see that there is only one view update, not two. The reason is that the object INFO listens, but the array NUMs does not. Why is that?

In a sense, NUMS can go deep, but the Object.defineProperty() API doesn’t have the ability to listen for arrays, so we need to create a layer that can listen for arrays.

(2) Listen on array

To give the Object.defineProperty() API the ability to listen on arrays, we can do this. The specific code is as follows:

// Trigger to update the view
function updateView() {
    console.log('View Update')}// Redefine the array prototype
const oldArrayProperty = Array.prototype
// Create a new object, the prototype points to oldArrayProperty, and extending the new method does not affect the prototype
const arrProto = Object.create(oldArrayProperty);
['push'.'pop'.'shift'.'unshift'.'splice'].forEach(methodName= > {
    arrProto[methodName] = function () {
        updateView() // Triggers a view update
        oldArrayProperty[methodName].call(this. arguments)// Array.prototype.push.call(this, ... arguments)}})// Redefine attributes to listen
function defineReactive(target, key, value) {
    // Deep monitor
    observer(value)

    / / core API
    Object.defineProperty(target, key, {
        get() {
            return value
        },
        set(newValue) {
            if(newValue ! == value) {// Deep monitor
                observer(newValue)

                // Set the new value
                // Note that value is always in the closure, and after this is set, the latest value will be retrieved when we get it
                value = newValue

                // Trigger to update the view
                updateView()
            }
        }
    })
}

// Listen for object properties
function observer(target) {
    if (typeoftarget ! = ='object' || target === null) {
        // Not objects or arrays
        return target
    }

    // Contaminate the global Array prototype (if defined directly inside it, it will contaminate the global)
    // Array.prototype.push = function () {
    // updateView()
    / /...
    // }

    if (Array.isArray(target)) {
        target.__proto__ = arrProto
    }

    // Redefine attributes (for in can also iterate over arrays)
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
}

// Prepare data
const data = {
    name: 'monday'.age: 20.info: {
        address: 'the fuzhou' // Deep listening is required
    },
    nums: ['Play basketball'.'Come out and play'.'Play table tennis']}// Listen for data
observer(data)

/ / test
data.info.address = 'Shanghai' // Deep monitor
data.nums.push('神游') // Listen for arrays

Copy the code

The browser displays the following output:

We can see that the corresponding views for both data are updated. By redefining the array stereotype, we let Object.defineProperty() implement the ability to listen on an array.

(3) Several disadvantages

Having made Object.defineProperty() responsive, let’s summarize some of its drawbacks:

1) Deep monitoring, need to recurse to the end, one-time calculation is large

When traversing an object or array, you need to do deep listening, that is, you need to recurse to the end, which can make a one-time calculation very large. (This problem has been solved in VUe3.0 by not recursing all at once, but by recursing whenever we use it. This will be covered in a later article.)

2) Unable to listen on new attributes/deleted attributes

Object.defineproperty () does not update the view when adding or deleting attributes, that is, it does not listen to the data. This should be paid special attention in normal development! Otherwise, sometimes we get data and somehow we don’t know what we’re doing wrong. The usual solution to this problem is to use vue. set and vue. delete to add and delete attributes, which solves the problem of data not being listened on.

3) Unable to use native listening array, requires special handling

The Object.defineProperty() API itself cannot listen on native arrays, so you need to redefine the array stereotype to listen on arrays.

Fourth, concluding remarks

So much for the response formula principle for vue2. X! From the above analysis, we can see that Object.defineProperty() has some advantages, but also some disadvantages. Therefore, Vue3.0 uses Proxy to solve the problems mentioned above. However, Proxy has not been fully promoted until now, because Proxy has compatibility problems, such as incompatible WITH IE11, and Proxy cannot polyfill. So vue2 2.x should be around for a long time. Therefore, for vue2. X and VUe3.0, both of them have to be learned, rather than saying vue3.0 and not learning vue2. X, for the two, more of a complementary result.

This is the end of the chat, for the vUE principle of learning have deeply feel the joy of making wheels, but gnaw source code at the beginning of learning will really be more boring. Hope to make persistent efforts, strive for more VUE source code, read more principles!

  • Pay attention to the public number Monday laboratory, the first time to pay attention to learning dry goods, more selected columns for you to unlock ~
  • If this article is useful to you, be sure to like it and follow it
  • See you next time! 🥂 🥂 🥂