The preface

Company developed a drag and drop form project recently, with the help of the Vue, boss began to study the Vue source code, and teach us, the boss said, read the source code can not only read the source code, still have to read his design idea, why is he so design, like a designer to read, so you can understand, this article, I will talk about the responsive principle of Vue and its design ideas according to the guiding direction of the eldest brother and my own understanding

What is a Vue response

Official explanation: One of the most unique features of Vue is its non-invasive and responsive system. Data models are just plain JavaScript objects. And when you change them, the view updates, which is basically the data changes and the view updates accordingly, the view changes.

For example, input input, the data will be changed accordingly.

If we look at an example graph of the response, which is an addition expression for an Excel table, we will see that when the addend changes, its sum changes automatically, without the need for human manipulation to re-evaluate the result. The relationship between data and views in Vue is similar to the relationship between addends and sums here.So how is Vue responsive implemented? To know, we need to understand his design pattern.

Responsive design patterns

The design pattern used by Vue responsiveness is the observer pattern, which is colloquially referred to as.

For example: Guo teacher to work every day with an orange, I want to rub a bite when he eats oranges, but I don’t want to keep staring at Guo teacher to see when he eats, so I and Guo teacher agreed, you eat oranges notice me. Then guo teacher saw, also want to eat, is also, to make an appointment with miss guo guo teacher also notify her eat oranges, and when miss guo eat oranges, think of me and he agreed and guo teacher, then send notification, guo teacher messages are received, and I do what I want to do things as soon as (gather together in the past, and he eat together).

In programming, this saves the resource cost of repeated retrieval and allows for faster feedback.

  1. Observation Object (Subject) : A method that has the two necessary identifiers to notify the observer owned by the current instance. A method to add an observer to the current instance so you know who to notify.
  2. Observer: A method that has a necessary identifier to notify an instance of status updates.

Contact information:

  1. Through its own internal notification function, the Subject calls the callback function corresponding to all observers in the list of observers to notify observers.
  2. An Observer passes itself to its list of observers by calling the add method on the Subject.

A fusion of the responsive and observer modes

Let’s first take a look at what files are needed to implement a basic Vue, let’s start with a large oneminiVueAs a demo of the discussion.

Let’s first introduce what these files do one by one;

  1. Vue is an event bus file, which will take the data from the object description text, and then hijack all the data through the _proxyData function to facilitate access to the subsequent properties.
  2. The observer file, the walk method here, recurses through each property in the data. Then create a new Dep for each attribute in defineReactive to store its own dependencies (observers), the root of the object.defineProperty response, and add its dep. target to the Dep list if it triggers a GET. So this step is collecting dependencies, so if you take my value, you’re interested in me, so I’m going to add you to my observer table, and if I hit set, I’m going to hit notify in DEP, and I’m going to tell all observers that the data has been updated, so go ahead.
  3. The DEp file is a Subject in the observer mode, which has a container to store the observations, methods to add observers, and methods to notify observers.
  4. The compiler file is a processing file designed to handle collections of parsing instructions, difference expressions, and so on.
  5. The watcher file is the observer in the observer mode, which has a method to update the view and calls the deP method to add the observer.

We see that there are only three files left in addition to the event bus file and the processing file, and these three files are the root of the Vue response. Let’s look at the diagram

summary

We can see that the DEp file and the watcher file, which is an implementation of the observer mode, and the observe file is the bridge between them, by hijacking the GET and set operations, telling the DEP when to add an observer, and informing the observer, which forms automation. When data is being read, Create a connection between the observer and the observed, and notify the observed to send a notification message when the data changes, thus achieving responsiveness.

The Vue instance is initialized

Let’s take a look at the source code for each of these steps

  1. The first is the entry file vue.js. throughdefinePropertyComplete the proxy for all Data in Data.
class Vue {
    constructor (options) {
    
        this.$options = options || {} // save options
        this.$el = typeof options.el === 'string' ? document.querySelector(options.el)
        :options.el // get dom
        this.$data = options.data // get data
        this.$methods = options.methods
        // 1. Data All data hijacked agent
        this._proxyData(this.$data)
        // 2. Call the observe object to listen for data changes
        new Observer(this.$data)
        // 3. Call the Compiler object to parse the instructions and difference expressions
        new Compiler(this)
    }
    _proxyData (data) {
        // Iterate over all data
        Object.keys(data).forEach(key= > {
            // Hijack each data via defineProperty
            Object.defineProperty(this, key, {
                enumerable: true.configurable: true,
                get () {
                    return data[key]
                },
                set (newValue) {
                    if (data[key] === newValue) {
                        return
                    }
                    data[key] = newValue
                }
            })
        })
    }
}

Copy the code
  1. Then go to observe-js and iterate over all the data, collecting dependencies if it’s GET and sending notifications if it’s set
class Observer {
    constructor(data) {
        this.walk(data)
    }
    walk (data) { // Loop through data
        if(! data ||typeofdata ! = ='object') {
            return
        }
        Object.keys(data).forEach(key= > {
            this.defineReactive(data, key, data[key])
        })
    }
    defineReactive (obj, key, val) { 
        let that = this
        this.walk(val) // If val is an object, give it a method that fires when it is bound to get and set
        let dep = new Dep() // Responsible for collecting dependencies and sending notifications
        Object.defineProperty(obj, key, {
            configurable: true.enumerable: true.get() {
                Dep.target && dep.addSub(Dep.target) // Collect dependencies
                return val // If obj[key] is used, it becomes an infinite loop
            },
            set(newValue) {
                if (newValue === val) {
                    return
                }
                val = newValue
                that.walk(newValue) // This may be an object, which is called inside the set function
                dep.notify() // Send a notification}}}})Copy the code

Summary: This file is a reactive automation implementation that uses a walk method to recursively iterate through each property in the data, then creates a new Dep for each object in defineReactive to store its own dependencies (observers), and then opens the object’s enumerable and writable properties. And define a method when a set and get are triggered.

If a GET is triggered, add its dep. target to the Dep list. This step is collecting dependencies. You took my note that you were interested in me, so I added you to my observer list. If set is triggered, it indicates that the data has changed, and the notify in DEP is triggered, notifying all observers that the data has been updated, and acting quickly, so as to achieve a response.

  1. Then go to watcher.js. In this case, a static property of Dep class target is used to record the current watcher object. By using the property of Dep class, trigger GET, and make its own dependency added to the list of observers to form the observer pattern. Then, Dep. Upon receiving notification from the observed object, the view updates by calling its own update method.
class Watcher {
    constructor (vm, key, cb) {
        this.vm = vm
        // Attribute name in data
        this.key = key
        // The callback function updates the view
        this.cb = cb
        // Record the watcher object to the static property target of the Dep class
        Dep.target = this
        // Triggers the GET method, which calls addSub
        this.oldValue = vm[key]
        Dep.target = null
    }
    // Notify the view to update when data changes
    update () {
        let newValue = this.vm[this.key]
        if (this.oldValue === newValue) {
            return
        }
        this.cb(newValue)
    }
}
Copy the code

Let’s take a final look at the initialization flowchart

According to the arrow, the first initialization phase is done in three steps:

  1. Call _proxyData in vue.js for data hijacking.
  2. Go to observe.js and create a Dep (object to Observe), which has a container to store a list of observers, and a method to add observers and notify observers.
  3. Create a Watcher. He is an observer in the observer pattern, so he has a response method to notify updates.

Summary: These three steps are the core of Vue responsiveness and the implementation of the observer pattern, where the Watcher adds itself to the list of observers to the object of observation by triggering get. Dep realizes the response by notifying all observers by traversing its own observer list.

View rendering and view updating

Let’s take a look at the main content of the compiler.js file

  1. Compile: Edit the template
  2. CompileElement: compileElement nodes
  3. CompileText: Compilestext nodes to handle differential expressions
/ / parsing v - model
    modelUpdater (node, value, key) {
        node.value = value
        new Watcher(this.vm, key, (newValue) = > { // Create a watcher object to update the view when data changes
            node.value = newValue
        })
        // Bidirectional binding
        node.addEventListener('input'.() = > {
            this.vm[key] = node.value
        })
    } 
// Compile the template
    compile (el) {
        let childNodes = el.childNodes
        Array.from(childNodes).forEach(node= > {
            if (this.isTextNode(node)) { // Process text nodes
                this.compileText(node)
            }   else if(this.isElementNode(node)) { // Process element nodes
                this.compileElement(node)
            }
            // If there are children, call recursively
            if (node.childNodes && node.childNodes.length > 0) {
                this.compile(node)
            }
        })
    }
    // Compile element nodes to process instructions
    compileElement (node) {
        // console.log(node.attributes)
        if (node.attributes.length) {
            Array.from(node.attributes).forEach(attr= > { // Iterate over all element nodes
                let attrName = attr.name
                if (this.isDirective(attrName)) { // Check whether it is an instruction
                    attrName = attrName.indexOf(':') > -1 ? attrName.substr(5) : attrName.substr(2) // Get the value after v-
                    let key = attr.value // Get the data name
                    this.update(node, key, attrName)
                }
            })
        }
    }
 // Compile the text node to handle differential expressions
    compileText (node) {
        // Get the value in {{}}
        // console.dir(node) // console.dir => Convert to object
        let reg = / \ {\ {(. +?) \} \} /
        let value = node.textContent
        if (reg.test(value)) {
            let key = RegExp.$1.trim() // Return the first matched string, excluding Spaces
            node.textContent = value.replace(reg, this.vm[key])
            new Watcher(this.vm, key, (newValue) = > { // Create a watcher object to update the view when data changes
                node.textContent = newValue
            })
        }
    }
Copy the code

Let’s look at another flow chart

Summary: Compile converts elements into data models, which are ordinary JavaScript objects called vNode objects, and then traverses vNode objects, which are classified into element nodes, text nodes and data according to the identifier, respectively into different processing functions. And create a Watcher object, and then trigger get in the Watcher object to achieve the response, the synchronization will updata update data, converted to the real DOM, complete the page rendering, the update is so repeated.

Overall response flow chart

Finally, let’s do a knowledge review according to the flow chart. The first step is to initialize the three steps:

  1. Using _proxyData to hijack Data
  2. Create a Dep (Observe target) object,
  3. Then create the Watcher object,

Then start the render phase

  1. Template compilation occurs after vm.render () is triggered
  2. Complie (template) compiles the element into a VNode object, traverses the object, creates a Watcher, and adds dependencies to complete the response.
  3. Updata updates the data, then converts it to the real DOM and completes the rendering.

So far, Vue responsive principle and its design pattern should be very clear, if you have questions, welcome to leave a message.

Welcome friends who want to learn and progress together, join my study group, where you can discuss advanced skills and share your latest learning content!

If you think the article is good, you can click a like, give the author a little encouragement, thank you, you can choose to add my wechat friend, I will pull you into the group.