Before talking about the Vue responsive principle, we need to be familiar with the reduce method in the array, and can skillfully use the Reduce () method to solve the needs of the problem, for the following understanding of the Vue responsive principle in advance to heat up the brain. Let’s start by introducing the reduce () method.

Reduce () method

Reduce () : Loops through the current array, and the next operation depends on the last return value, which is equivalent to a “snowball” operation.

Reduce ((result of last calculation, current loop item) => {return result of last calculation + current loop item}, initial value)Copy the code

Application scenario: The initial value of the next operation depends on the return value of the last operation.

Realize the operation of numerical accumulation

When we add up a number, the common thing to think about is iterating through it using forEach ().

const array = [1.2.3.4.5]
let sum = 0
array.forEach(item= > {
    sum += item
})
console.log(sum) / / 15
Copy the code

In the summation above, I wrote too many lines of code and needed to declare an external variable to store the value. It seems wordy and cumbersome. However, we can use the reduce () method to quickly implement the numerical accumulation operation.

const array = [1.2.3.4.5]
const sum =  array.reduce((pre, next) = >  pre + next,0)
console.log(sum) / / 15
Copy the code

Both methods implement summation, but the reduce () method looks much cleaner than the forEach () method.

Implement object chain value operation

The power of the reduce () method is to realize not only the sum operation of the number, but also the operation of the chain value of the object, etc.

const obj = {
    name: 'alex'.info: {
        address: {
            location: 'gz'}}}const array = ['info'.'address'.'location']
const value = array.reduce((pre, next) = > pre[next], obj)
console.log(value) // gz
Copy the code

Chain gets the value of an object property upgrade operation

const obj = {
    name: 'alex'.info: {
        address: {
            location: 'gz'}}}const objInfo = 'info.address.loaction'
const value =objInfo.split('. ').reduce((pre, next) = > pre[next], obj)
console.log(value) // gz
Copy the code

Ok, with the reduce () method above, I believe you are in the mood, and now we are on topic.

Publish and subscribe model

Here’s a simple example: Existing supply Pepsi cola soda shop, at that moment, to the A, B, C three people, is to buy Pepsi, but the Pepsi sold out of the shop, the manager took out A notebook to record respectively their contact information, wait for Pepsi, the arrival of the goods, then take out the book one-to-one inform their three people to take goods, Then A, B, and C each take the Pepsi to do something else. In this example, people A, B, and C can be understood as Watcher subscribers, and the store can be understood as A Dep, which is responsible for collecting dependencies and notifting Watcher, and Watcher can then do related things (such as re-rendering the page or updating the data-driven view) after receiving the notification.

From the simple example above, we also know that the Dep class should have the following functions:

  • Responsible for dependency collection.
  • First, there is an array that holds all the subscription information.
  • Second, you provide an addSub () method that appends a subscription to the array.
  • Finally, a loop is provided that triggers each subscription message in the array.

The Watcher class is responsible for subscribing to events, mainly a callback function

Vue responsive principle

From the above examples, we have a good understanding of what the publish subscribe model is and what the Dep class and Watcher class can do. Let’s take a closer look at the principle of Vue responsiveness. The core method of the Vue responsivity principle is the hijacking of attributes through Object.defineProperty() to listen for data changes. This method is one of the most important and basic in this article.

To implement Vue’s bidirectional data binding, the following must be implemented:

  1. To implement aDep, is mainly used to collect dependencies (subscribers) and notify corresponding subscribers to update data.
  2. Implement a data listenerObserver, can listen on all attributes of the data object, addsetter, andgetterMethod to notify dependent collection objects of the latest value if there is a change (Dep) and inform subscribers (Watcher) to update view changes.
  3. Implement a parserCompile, scan and parse the instructions of each element node, replace data according to the instructions, and bind the corresponding update function.
  4. To implement a Watcher, as a connectionObserverCompileIs able to subscribe to and receive notifications of each property change, execute the corresponding callback function, and update the view.

Implement the parser Compile

Compile implements a parser to scan and parse instructions of each element node, replace data according to instructions, and bind the corresponding update function.

Initialize the

class MVue {
    constructor (options) {
        this.$el = options.el,
        this.$data = options.data,
        this.$options = options
        // Start compiling if a template template exists
        if (this.$el) {
            // create the parser Compile
            new Compile(this.$el, this)}}}class Compile {
    constructor (el, vm) {
        this.vm = vm
        // Check if it is an element node
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
    }
    isElementNode (node) {
        // node.nodeType = 1 is the element node; nodeType = 3 is the text node
        return node.nodeType === 1}}Copy the code

Creating document fragments

Because every time the match is replaced, the page will be backflowed and redrawn, affecting the performance of the page, so it is necessary to create document fragments to cache and reduce the backflowed and redrawn pages.

class MVue {
    constructor (options) {
        this.$el = options.el,
        this.$data = options.data,
        this.$options = options
        // Start compiling if a template template exists
        if (this.$el) {
            // create the parser Compile
            new Compile(this.$el, this)}}}class Compile {
    constructor (el, vm) {
        this.vm = vm
        // Check if it is an element node
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        // Because every time the match is replaced, the page will be backflowed and redrawn, affecting the performance of the page
        // Document shards need to be created to cache and reduce page backflow and redraw
        console.log(this.el);
        const framgent = this.createFramgent(this.el)
        // Add the document fragment to the root element and render it to the page
        this.el.appendChild(framgent)
    }
    // Create a document fragment
    createFramgent (node) {
        const framgent = document.createDocumentFragment(node)
        // The loop adds nodes in turn to the document fragment firstChild contains a space newline character
        // console.log(node.firstChild);
        let children
        while (children = node.firstChild) {
            // Append in sequence to the document fragment
            framgent.appendChild(children)
        }
        return framgent
    }
    isElementNode (node) {
        // node.nodeType = 1 is the element node; nodeType = 3 is the text node
        return node.nodeType === 1}}Copy the code

Compile templates recursively

class MVue {
    constructor (options) {
        this.$el = options.el,
        this.$data = options.data,
        this.$options = options
        // Start compiling if a template template exists
        if (this.$el) {
            // create the parser Compile
            new Compile(this.$el, this)}}}class Compile {
    constructor (el, vm) {
        this.vm = vm
        // Check if it is an element node
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        // Because every time the match is replaced, the page will be backflowed and redrawn, affecting the performance of the page
        // Document shards need to be created to cache and reduce page backflow and redraw
        console.log(this.el);
        const framgent = this.createFramgent(this.el)

        // Start compiling the template
        this.compile(framgent)

        // Add the document fragment to the root element and render it to the page
        this.el.appendChild(framgent)
    }
    compile (framgent) {
        const childNodes = framgent.childNodes
        console.log(childNodes)
        // Walk through all the nodes and determine whether they are element nodes or text nodes
        // Convert a pseudo-array to a true array
        const childNodesArray = Array.from(childNodes)
        childNodesArray.forEach(node= > {
            if(this.isElementNode(node)){
                // is the element node
                console.log(node);
            } else {
                // is a text node
                console.log(node);
            }
            // Multilevel nesting requires recursive child elements
            if(node.childNodes && node.childNodes.length){
                this.compile(node)
            }
        })
    }
    // Create a document fragment
    createFramgent (node) {
        const framgent = document.createDocumentFragment(node)
        // The loop adds nodes in turn to the document fragment firstChild contains a space newline character
        // console.log(node.firstChild);
        let children
        while (children = node.firstChild) {
            // Append in sequence to the document fragment
            framgent.appendChild(children)
        }
        return framgent
    }
    isElementNode (node) {
        // node.nodeType = 1 is the element node; nodeType = 3 is the text node
        return node.nodeType === 1}}Copy the code

Parse compiled elements

class MVue {
    constructor(options) {
        this.$el = options.el,
            this.$data = options.data,
            this.$options = options
        // Start compiling if a template template exists
        if (this.$el) {
            // create the parser Compile
            new Compile(this.$el, this)}}}class Compile {
    constructor (el, vm) {
        this.vm = vm
        // Check if it is an element node
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        // Every time the match is replaced, the page will be backflowed and redrawn, affecting the performance of the page
        // Document shards need to be created to cache and reduce page backflow and redraw
        console.log(this.el);
        const framgent = this.createFramgent(this.el)

        // Start compiling the template
        this.compile(framgent)

        // Add the document fragment to the root element and render it to the page
        this.el.appendChild(framgent)
    }
    compile (framgent) {
        const childNodes = framgent.childNodes
        // console.log(childNodes)
        // Walk through all the nodes and determine whether they are element nodes or text nodes
        // Convert a pseudo-array to a true array
        const childNodesArray = Array.from(childNodes)
        childNodesArray.forEach(node= > {
            if(this.isElementNode(node)){
                // is the element node
                // console.log(node);
                this.compileElement(node)
            } else {
                // is a text node
                // console.log(node);
                this.compileText(node)
            }
            // Multilevel nesting requires recursive child elements
            if(node.childNodes && node.childNodes.length){
                this.compile(node)
            }
        })
    }
    // Parse the compiled element node
    compileElement (elementNode) {
        // Compile the element to get the attributes of the element node through attributes, which contain name and value
        const attributes = elementNode.attributes;
        [...attributes].forEach(attr= > {
            // name Attribute name v-text V-html value Attribute value obj.name obj.age
            const {name, value} = attr 
            if (this.isDirective(name)) {
                / / instructions
                // Deconstruct v-text v-html
                const [,directive] = name.split(The '-')
                const [dirName, eventName] = directive.split(':')
                // Can Riemann store this instruction in compileUtils
                compileUtils[dirName] && compileUtils[dirName](elementNode, value, this.vm, eventName)
                // Attributes in the sequence tag
                elementNode.removeAttribute('v-' + directive)
            } else if (this.isEventName(name)) {
                / / is the event
                const [,eventName] = name.split(The '@')
                // Process different data according to different instructions text HTML model
                compileUtils['on'](elementNode, value, this.vm, eventName)
            }
        });
    }
    // Is it a command
    isDirective (name) {
        // Start with v-
        return name.startsWith('v-')}// Whether it is an event
    isEventName (name) {
        // Start with @
        return name.startsWith(The '@')}// Parse and compile text nodes
    compileText (textNode) {
        // Compile text
    }
    // Create a document fragment
    createFramgent (node) {
        const framgent = document.createDocumentFragment(node)
        // The loop adds nodes in turn to the document fragment firstChild contains a space newline character
        // console.log(node.firstChild);
        let children
        while (children = node.firstChild) {
            // Append in sequence to the document fragment
            framgent.appendChild(children)
        }
        return framgent
    }
    isElementNode (node) {
        // node.nodeType = 1 is the element node; nodeType = 3 is the text node
        return node.nodeType === 1}}Copy the code

Parse compiled text

// Parse and compile text nodes
    compileText (textNode) {
        // Compile text
        // Get the text content
        const content = textNode.textContent
        // Regex matches
        const reg = / \ {\ {(. +?) \} \} /
        if(reg.test(content)) {
        // Process different data according to different instructions text HTML model
            compileUtils['text'](textNode, content, this.vm)
        }
    }
Copy the code

So if you’re wondering what compileUtils are and what they’re supposed to handle, compileUtils is an object that handles different instructions, v-text handles text, V-HTML handles HTML elements, V-model is…. for processing form data There’s a updater in compileUtils that has methods for updating views.

CompileUtils object

const compileUtils = {
    // Get the value of the attribute in data
    getValue (value, vm) {
        // Split. Into an array, and then use reduce to get the attribute values in data
        return value.split('. ').reduce((pre, next) = > {
            return pre[next]
        },vm.$data)
    },  
    text (node , value, vm) { // value may be {{obj.name}} may be obj.age
        let val
        if (value.indexOf('{{')! = = -1) {
            {{obj. Name}}
            // Perform global matching
            val = value.replace(/ \ {\ {(. +?) \}\}/g.(. args) = > {
                return this.getValue(args[1], vm)
            })
        } else {
            // obj.age
            val =  this.getValue(value, vm)
        }
        // Update/replace data
        this.updater.textUpdata(node, val)
    },
    html (node, value, vm) {
        const val = this.getValue(value, vm)
        // Update/replace data
        this.updater.htmlUpdata(node,val)
    },
    model (node, value ,vm) {
        const val = this.getValue(value, vm)
        this.updater.modleUpdata(node, val)
    },
    on (node, value, vm, eventName) {
        // Get the callback function
        let fn = vm.$options.methods && vm.$options.methods[value]
        node.addEventListener(eventName, fn.bind(vm), false)},// there is a method for updating the view corresponding to the directive
    updater:{
        textUpdata (node, value) {
            node.textContent = value
        },
        htmlUpdata (node, value) {
            node.innerHTML = value
        },
        modleUpdata (node, value) {
            node.value = value
        },
    }
}
Copy the code



According to the above flow chart, we have completed the step of new MVVM () -> Compile -> Updater, which implements the initialization of the page view data display. Next we proceed from Observer -> Dep -> Watcher -> Updater.

Implement a listener Observer

The purpose of the Observer is to hijack the listener for each property in the object, set setters and getters, and call notify() in the Dep to notify the subscriber of any changes.

We can use object.defineProperty () to listen for property changes, and use the observe method to recursively traverse the data of the Object, including the property of the child property Object, adding setters and getters. When we assign a value to an Object or get a value, I’m going to fire setter and getter methods, and I’m going to be able to listen for changes in the data.

class Observer{
    constructor (data) {
        this.observe(data)
    }
    observe (data) {
        // Do not consider array data and data is an object
        if (data && typeof data === 'object') {// Iterate over the key of the object
            Object.keys(data).forEach(key= > {
                // Pass the object key into the value
                this.defineReactive(data, key, data[key])
            })
        }
    }
    defineReactive (obj, key, value) {
        // recursive traversal
        this.observe(value)
        Object.defineProperty(obj, key, {
            configurable: false.enumerable: true,
            get () {
                return value
            },
            set: (newValue) = > {
                if( newValue ! = value) {// If you assign to an object directly, you also need to listen for data on that object
                    this.observe(newValue)
                    value = newValue
                    // dep.notify Notifies watcher of changes to update the view
                    dep.notify()
                }
            }
        })
    }
}
Copy the code

Depends on the collection object Dep

We can use the above Observer to monitor the data of each object property, and use the notify method of deP to notify the subscriber (Watcher) to perform the callback function to change the view when the data changes.

Dep role:

  • Create an array to hold subscribers and declare methods to add subscribers (addSub)
  • Declare a notification subscription method (notify)
class Dep {
    constructor () {
        / / store watcher
        this.sub = []
    }
    // Add subscribers
    addSub (watcher) {
        // Place the subscribers in an array
        this.sub.push(watcher)
    }
    // Notify subscribers
    notify () {
        // Loop through the watcher bound callback function in turn
        this.sub.forEach(watcher= > watcher.update())
    }

}
Copy the code

The Watcher subscriber

Watcher acts as a bridge between the Observer and Compile, subscribing to and receiving notification of each property change, and executing the corresponding bound callback function to update the view. Watcher must have three things:

  1. In their ownInstantiate to add itself to the Dep.
  2. You have to have oneUpdate () method.
  3. Wait for the attribute value to changeDep. Notify ()To be able to call its ownUpdate () methodAnd,Triggers the callback function bound in Compile.
class Watcher{
    constructor (vm, value, callback) {
        // The vm holds the latest values
        // value Specifies the attribute of the data change
        // Bind the callback function
        this.vm = vm
        this.value = value
        this.callback = callback
        // Store old values
        this.oldValue = this.getOldValue()
    }
    // The getter is triggered indirectly to get the value
    getOldValue () {
        Dep.target = this
        const olaValue = compileUtils.getValue(this.value, this.vm)
        Dep.target = null
        return olaValue
    }
    update () {
        Dep notifies the subscriber of changes in operation data through notify and the subscriber updates the view
        const newValue = compileUtils.getValue(this.value, this.vm)
        if(newValue ! = =this.oldValue) {
            this.callback(newValue)
            this.oldValue = newValue
        }
    }
}
Copy the code

At this point, we have written the required Observer, Watcher, Compiler, and Dep. So, all that’s left is how to connect them together to form a loop, and that’s what we’re going to do with the responsive form. When we open the page, the first implementation is Observer first to listen to and hijack the data of each attribute of the object, and then use the Compile parser to parse, the first step: we can make sure that the new Observer and then new Compile

class MVue {
    constructor(options) {
        this.$el = options.el,
            this.$data = options.data,
            this.$options = options
        // Start compiling if a template template exists
        if (this.$el) {
            // 1. Data hijacking
            new Observer(this.$data)
            // create parser Compile
            new Compile(this.$el, this)}}}Copy the code

The dep. Notify method is triggered when the data is modified. So we can make sure that new Dep collects dependencies and triggers notify after recursion before Object.defineProperty, And we can trigger the dep.addSub method in the getter to add the Watcher instance to the array, so we have a collection of dependent objects.

class Observer{
    constructor (data) {
        this.observe(data)
    }
    observe (data) {
        // Do not consider array data and data is an object
        if (data && typeof data === 'object') {// Iterate over the key of the object
            Object.keys(data).forEach(key= > {
                // Pass the object key into the value
                this.defineReactive(data, key, data[key])
            })
        }
    }
    defineReactive (obj, key, value) {
        // recursive traversal
        this.observe(value)
        const dep = new Dep()
        Object.defineProperty(obj, key, {
            configurable: false.enumerable: true,
            get () {
                // Add subscribers to the collection dependency array if there are any
                Dep.target && dep.addSub(Dep.target)
                return value
            },
            set: (newValue) = > {
                if( newValue ! = value) {// If you assign to an object directly, you also need to listen for data on that object
                    this.observe(newValue)
                    value = newValue
                    // dep.notify Notifies watcher of changes to update the view
                    dep.notify()
                }
            }
        })
    }
}
Copy the code

In the end, all that is left is how Complie and Watcher relate to each other. In Compile, we declare a compileUtils for each instruction. Inside this compileUtils, we declare an updater. It contains data updates for various instructions, such as textUpdate, htmlUpdate, modelUpdate, etc. So, we found the function to update data, we can confirm that before updating the function, new Watcher, The subscriber instance (Watcher) is triggered when the data changes to trigger the bound callback function, updating the view.

const compileUtils = {
    // Get the value of the attribute in data
    getValue (value, vm) {
        // Split. Into an array, and then use reduce to get the attribute values in data
        return value.split('. ').reduce((pre, next) = > {
            return pre[next]
        },vm.$data)
    },  
    // {{obj.name}}-- {{obj.age}} get the value again to avoid changing one and two at the same time
    getContentValue (value, vm) {
        return value.replace(/ \ {\ {(. +?) \}\}/g.(. args) = > {
            return this.getValue(args[1], vm);
        })
    },
    text (node , value, vm) { // value may be {{obj.name}} may be obj.age
        let val
        if (value.indexOf('{{')! = = -1) {
            {{obj. Name}}
            // Perform global matching
            val = value.replace(/ \ {\ {(. +?) \}\}/g.(. args) = > {
                / /... Args prints the three values of the current match, the smallest match in the string, and the original string
                new Watcher(vm, args[1].() = > {
                    this.updater.textUpdata(node, this.getContentValue(value, vm))
                })
                return this.getValue(args[1], vm)
            })
        } else {
            // obj.age
            val =  this.getValue(value, vm)
        }
        // Update/replace data
        this.updater.textUpdata(node, val)
    },
    html (node, value, vm) {
        const val = this.getValue(value, vm)
        new Watcher(vm, value, (newValue) = > {
            this.updater.htmlUpdata(node, newValue)
        })
        // Update/replace data
        this.updater.htmlUpdata(node,val)
    },
    model (node, value ,vm) {
        const val = this.getValue(value, vm)
        new Watcher(vm, value, (newValue) = > {
            this.updater.modleUpdata(node, newValue)
        })
        this.updater.modleUpdata(node, val)
    },
    on (node, value, vm, eventName) {
        // Get the callback function
        let fn = vm.$options.methods && vm.$options.methods[value]
        node.addEventListener(eventName, fn.bind(vm), false)},updater:{
        textUpdata (node, value) {
            node.textContent = value
        },
        htmlUpdata (node, value) {
            node.innerHTML = value
        },
        modleUpdata (node, value) {
            node.value = value
        },
    }
}
Copy the code



After the above analysis, we have completed the update of data change to view, but in the form, the update of view change to data has not been realized, so we still need to analyze the form data.

/ Handle different data according to different instructions text HTML modelconst compileUtils = {
    // Get the value of the attribute in data
    getValue (value, vm) {
        // Split. Into an array, and then use reduce to get the attribute values in data
        return value.split('. ').reduce((pre, next) = > {
            return pre[next]
        },vm.$data)
    },  
    getContentValue (value, vm) {
        return value.replace(/ \ {\ {(. +?) \}\}/g.(. args) = > {
            return this.getValue(args[1], vm); })},// View -> Data
    setValue (vm,value,inputValue) {
        
        return value.split('. ').reduce((pre, next) = > {
            if (typeofpre[next] ! = ='object') {
                // If it is not an object, it is directly assigned
                pre[next] = inputValue
            } 
            // The value of the object is direct
            return pre[next]
        },vm.$data)
    },
    text (node , value, vm) { // value may be {{obj.name}} may be obj.age
        let val
        if (value.indexOf('{{')! = = -1) {
            {{obj. Name}}
            // Perform global matching
            val = value.replace(/ \ {\ {(. +?) \}\}/g.(. args) = > {
                / /... Args prints the three values of the current match, the smallest match in the string, and the original string
                new Watcher(vm, args[1].() = > {
                    this.updater.textUpdata(node, this.getContentValue(value, vm))
                })
                return this.getValue(args[1], vm)
            })
        } else {
            // obj.age
            val =  this.getValue(value, vm)
        }
        // Update/replace data
        this.updater.textUpdata(node, val)
    },
    html (node, value, vm) {
        const val = this.getValue(value, vm)
        new Watcher(vm, value, (newValue) = > {
            this.updater.htmlUpdata(node, newValue)
        })
        // Update/replace data
        this.updater.htmlUpdata(node,val)
    },
    model (node, value ,vm) {
        const val = this.getValue(value, vm)
        // Data update -> View changes
        new Watcher(vm, value, (newValue) = > {
            this.updater.modleUpdata(node, newValue)
        })
        // View changes -> Data updates
        node.addEventListener('input'.(e) = > {
            this.setValue(vm, value, e.target.value)
        } ,false)
        this.updater.modleUpdata(node, val)
    },
    on (node, value, vm, eventName) {
        // Get the callback function
        let fn = vm.$options.methods && vm.$options.methods[value]
        node.addEventListener(eventName, fn.bind(vm), false)},updater:{
        textUpdata (node, value) {
            node.textContent = value
        },
        htmlUpdata (node, value) {
            node.innerHTML = value
        },
        modleUpdata (node, value) {
            node.value = value
        },
    }
}
class Compile {
    constructor (el, vm) {
        this.vm = vm
        // Check if it is an element node
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        // Every time the match is replaced, the page will be backflowed and redrawn, affecting the performance of the page
        // Document shards need to be created to cache and reduce page backflow and redraw
        console.log(this.el);
        let framgent = this.createFramgent(this.el)

        // Start compiling the template
        this.compile(framgent)

        // Add the document fragment to the root element and render it to the page
        this.el.appendChild(framgent)
    }
    compile (framgent) {
        const nodes = framgent.childNodes;
        // console.log(childNodes)
        // Walk through all the nodes and determine whether they are element nodes or text nodes
        // Convert a pseudo-array to a true array
        // const childNodesArray = Array.from(childNodes)
        [...nodes].forEach(node= > {
            if(this.isElementNode(node)){
                // is the element node
                // console.log(node);
                this.compileElement(node)
            } else {
                // is a text node
                // console.log(node);
                this.compileText(node)
            }
            // Multilevel nesting requires recursive child elements
            if(node.childNodes && node.childNodes.length){
                this.compile(node)
            }
        })
    }
    // Parse the compiled element node
    compileElement (elementNode) {
        // Compile the element to get the attributes of the element node through attributes, which contain name and value
        const attributes = elementNode.attributes;
        [...attributes].forEach(attr= > {
            // name Attribute name v-text V-html value Attribute value obj.name obj.age
            const {name, value} = attr 
            if (this.isDirective(name)) {
                / / instructions
                // Deconstruct v-text v-html
                const [,directive] = name.split(The '-')
                const [dirName, eventName] = directive.split(':')
                // If there is a function corresponding to this instruction
                compileUtils[dirName] && compileUtils[dirName](elementNode, value, this.vm, eventName)
                // Attributes in the sequence tag
                elementNode.removeAttribute('v-' + directive)
            } else if (this.isEventName(name)) {
                / / is the event
                const [,eventName] = name.split(The '@')
                compileUtils['on'](elementNode, value, this.vm, eventName)
            }
        });
    }
    // Is it a command
    isDirective (name) {
        // Start with v-
        return name.startsWith('v-')}// Whether it is an event
    isEventName (name) {
        // Start with @
        return name.startsWith(The '@')}// Parse and compile text nodes
    compileText (textNode) {
        // Compile text
        // Get the text content
        const content = textNode.textContent
        // Regex matches
        const reg = / \ {\ {(. +?) \} \} /
        if(reg.test(content)) {
            compileUtils['text'](textNode, content, this.vm)
        }
    }
    // Create a document fragment
    createFramgent (node) {
        const framgent = document.createDocumentFragment(node)
        // The loop adds nodes in turn to the document fragment firstChild contains a space newline character
        // console.log(node.firstChild);
        let children
        while (children = node.firstChild) {
            // Append in sequence to the document fragment
            framgent.appendChild(children)
        }
        return framgent
    }
    isElementNode (node) {
        // node.nodeType = 1 is the element node; nodeType = 3 is the text node
        return node.nodeType === 1}}class MVue {
    constructor(options) {
        this.$el = options.el,
            this.$data = options.data,
            this.$options = options
        // Start compiling if a template template exists
        if (this.$el) {
            // 1. Data hijacking
            new Observer(this.$data)
            // create parser Compile
            new Compile(this.$el, this)}}}Copy the code

Data proxy

In vue, we can directly use vm. MSG to get the data. In fact, it is the internal proxy for the data for us, equivalent to vm.$data. MSG, so we also need to do data proxy.

class MVue {
    constructor(options) {
        this.$el = options.el,
            this.$data = options.data,
            this.$options = options
        // Start compiling if a template template exists
        if (this.$el) {
            // 1. Data hijacking
            new Observer(this.$data)
            // Data broker
            this.proxy(this.$data)
            // create parser Compile
            new Compile(this.$el, this)}}// Data broker
    proxy(data) {
        for (const key in data) {
            Object.defineProperty(this, key, {
                get () {
                    return data[key]
                },
                set (newValue) {
                    data[key] = newValue
                }
            })
        }
    }
}
Copy the code

Sort out the whole process of Vue response

  1. When initializing a Vue instance,ObserverWill iterate over all attributes in data, usingObject.defineproperty () methodConvert all of these properties togetter/setter. And create a dependency collection objectdepA Dep instance for an attribute is used to manage all watchers under that attribute, or create multiple Watchers if the same attribute is used multiple times in a DOM node.
  2. Create when parsing instructionsWatcher instanceAnd thenThe updated function is placed on the callback of the Watcher instance.
  3. When the view is initialized, it reads the property value and firesgetterTo createAdd the Watcher instance to the DEP array.
  4. Trigger when data is modifiedsetter, the callDep. Notify methodNotifies the internal dePAll Wacther callback functions are executedAgain, renderThe current component generates a new virtual DOM tree.
  5. The Vue framework will be usedThe diff algorithmThe differences of each node in the new virtual DOM tree and the old virtual DOM tree are traversed and compared, and recorded. Finally, the loading operation partially modifies the recorded differences to the real DOM tree.

Interview Answer terms

Talk about your understanding of vUE’s MVVM responsive principle.

Vue adopts data hijacking combined with publishe-subscribe mode. It hijacks the getter and setter of each attribute through Object.defineProperty (), publishes messages to subscribers when data changes, and then triggers corresponding listener callback functions to update the view.

The Observer is required to recursively traverse the data, including the attributes of subobjects, adding getters and setters. When reading values or modifying data, getters or setters are triggered and data changes can be monitored.

Compile parses the instructions, initializes the page to replace the variables in the template with data, and binds the updated callback function to the node corresponding to each instruction, adds subscribers, once the data changes, subscribers are notified, triggering the callback to update the view.

Watcher is a bridge between the Observer and Compile. First, it needs to add itself to the deP in its own instance. Second, it needs to update the deP with an update method. Triggers the callback function bound in Compile.

MVVM, as the entry of data binding, integrates Observer, Compile and Watcher, uses Observer to monitor its model data changes, and uses Compile to parse instructions. Finally, Watcher is used to build a communication bridge between Observer and Compile to achieve data change -> view update; View Interactive Changes (INPUT) -> Bidirectional binding effect of data model updates.



That’s all about Vue responsiveness. To obtain the source code, click the link below.

Get source code stamp me!!