Core overall structure

Create an object prototype that accepts arguments as an object containing nodes, variables, and methods

/ / options object
{
            el: '#app'.data: {},methods: {}... . }Copy the code
class Vue( ){
    constructor(options){
      // Parameter variable assignment
        this.$options = options
        this.$el = options.el
        this.$data = options.data
         if (this.$el) {
            // 1. Implement a data observer
            new Observer(this.$data)
            // 2. Implement an instruction interpreter
            new Compile(this.$el, this)
            // Data hijacking
            this.proxyData(this.$data)
        }
  }
}
Copy the code

1. Data hijacking: real-time page updates

2. Instruction parser: Parse instructions starting with “V -“

3. Observer mode: When the value of a variable is changed, it can automatically notify all other objects concerned about the variable, and automatically update to obtain the new value of the variable.

The data was hijacked

Hijack each value of the data attribute to achieve bidirectional data binding

Syntax Object.defineProperty ()

Parameters:

Obj: indicates the target object

**prop:** The name of the property or method to define

Descriptor: attributes owned by the target attribute

List of features available for definition:

**value: The value of the ** attribute

**writable:** If false, the value of the property cannot be overridden.

Get: Calls back to this method once the target property has been accessed and returns the result of the operation to the user.

**set:** This method is called back once the target attribute is assigned.

Any attempt to delete the target property or modify the property (Writable, 64x, Enumerable) will be invalid if the 64x works without any additional control system.

Enumerable: Can I… Iterate through in loop or list in object. keys.


 proxyData(data) {
        for (const key in data) {
            Object.defineProperty(this, key, {
                get() {
                    return data[key]
                },
                set: newVal= > {
                    data[key] = newVal
                }
            })
        }
    }
Copy the code

Implements the instruction interpreter

Create an object prototype

Obtain the attribute information of all nodes. 1. Check whether the attribute of V – exists. 2. Check whether there are any children in the node

constructor(el, vm) {
        // Determine if it is an element node. If it is an element node, it is an element node
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        this.vm = vm

        // 1. Get the document fragment object and put it in memory to reduce page backflow and redraw
        const fragment = this.node2Fragment(this.el)
        // console.log(fragment);

        // 2. Compile the template
        // Get each child node
        let childNodes = fragment.childNodes
        // Convert the data childNodes to an array
        childNodes = this.convertToArray(childNodes)
       // Determine node information
        childNodes.forEach(child= > {
            if (this.isElementNode(child)) {
                // console.log(' element node ', child);
                this.compileElement(child)
            } else {
                // console.log(' document node ', child);
                this.compileText(child)
            }
            if (child.childNodes && child.childNodes.length) {
                this.compile(child)
            }
        });
        // 3. Append the child element to the root element
        this.el.appendChild(fragment)
    }
       convertToArray(nodes) {
        // A method to convert the data returned by childNodes to an array
        var array = null;
        / / Array. Prototype. Slice. Can call will have the length attribute object is converted to an Array of ie can't
        try {
            array = Array.prototype.slice.call(nodes, 0);
        } catch (ex) {
            array = new Array(a);for (var i = 0, len = nodes.length; i < len; i++) { array.push(nodes[i]); }}return array;
    }
Copy the code

If it is a V – instruction, remove the V – and carry the argument to the specified processing method

compileElement(node) {
        // Attributes returns a collection of attributes for the specified node
        const attributes = node.attributes
        // Convert the data returned by attributes to an array
        this.convertToArray(attributes).forEach(attr= > {
            const { name, value } = attr
            // console.log(value);
            //isDirective checks whether it starts with a V -
            if (this.isDirective(name)) {
                // is an instruction
                const [, dirctive] = name.split(The '-')
                const [dirName, eventName] = dirctive.split(':')
                // console.log(dirName, eventName);
                // Update data data-driven view
                compileUtil[dirName](node, value, this.vm, eventName)

                // Delete the instruction on the label
                node.removeAttribute('v-' + dirctive)
            }
        })
    }
Copy the code

Parameter Processing >>>

const compileUtil = {
    text(node, expr, vm) {
        let value
        if (expr.indexOf('{{')! = = -1) {
            value = expr.replace(/ \ {\ {(. +?) \}\}/g.(. args) = > {
                // Bind the observer to the corresponding view if the data changes in the future
                new Watcher(vm, args[1].(newVal) = > {
                    this.updater.textUpdater(node, this.getContentVal(expr, vm))
                })
                return this.getValue(args[1], vm)
            })
        } else {
            value = this.getValue(expr, vm)
            new Watcher(vm, expr, (newVal) = > {
                this.updater.textUpdater(node, newVal)
            })
        }

        this.updater.textUpdater(node, value)
    },
    html(node, expr, vm) {
        let value = this.getValue(expr, vm)
        new Watcher(vm, expr, (newVal) = > {
            this.updater.htmlUpdater(node, newVal)
        })
        this.updater.htmlUpdater(node, value)
    },
    model(node, expr, vm) {
        const value = this.getValue(expr, vm)
        // Bind update function data => view
        new Watcher(vm, expr, (newVal) = > {
            this.updater.modelUpdater(node, newVal)
        })
        // View => Data => View
        node.addEventListener('input'.e= > {
            / / set the value
            this.setValue(expr, vm, e.target.value)
        })
        this.updater.modelUpdater(node, value)
    },
    on(node, expr, vm, eventName) {
        let fn = vm.$options.methods && vm.$options.methods[expr]
        node.addEventListener(eventName, fn.bind(vm), false)},getValue(expr, vm) {
        expr = expr.replace(/\s+/g."")
        return expr.split('. ').reduce((data, currentVal) = > {
            // console.log(currentVal);
            return data[currentVal]
        }, vm.$data)
    },
    setValue(expr, vm, newVal) {
        expr = expr.replace(/\s+/g."")
        return expr.split('. ').reduce((data, currentVal) = > {
            data[currentVal] = newVal
        }, vm.$data)
    },
    updater: {
        textUpdater(node, value) {
            node.textContent = value
        },
        htmlUpdater(node, value) {
            node.innerHTML = value
        },
        modelUpdater(node, value) {
            node.value = value
        }
    },
    getContentVal(expr, vm) {
        return expr.replace(/ \ {\ {(. +?) \}\}/g.(. args) = > {
            return this.getValue(args[1], vm)
        })
    }
}
Copy the code

Observer mode Observer module

In three parts

  • Observer: Data observer.
  • Watcher: Data subscriber
  • Dep: Subscriber associated with 2.

When data changes, the Observer data observer informs the WWatcher data subscriber through the DEP to update the view

The Observer observers

class Observer {
    constructor(data) {
        this.observer(data)
    }

    observer(data) {
        if (data && typeof data === 'object') {
            Object.keys(data).forEach(key= > {
                this.defineReactive(data, key, data[key])
            })
        }
    }

    defineReactive(obj, key, value) {
        // Recursively iterate until the last value is not an object
        this.observer(value)
        const dep = new Dep()
        Object.defineProperty(obj, key, {
            enumerable: true.configurable: false.get() {
                // Add an observer to the Dep when the subscription data changes
                Dep.target && dep.addSub(Dep.target)
                return value
            },
            set: (newVal) = > {
                this.observer(newVal)
                // The new value is hijacked before the new value is updated
                value = newVal
                // Tell Dep to notify the change
                dep.notify()
            }
        })
    }
}
Copy the code

Dep notice

class Dep {
    constructor() {
        // Define an observer array
        this.subs = []
    }

    // Collect observers
    addSub(watcher) {
        this.subs.push(watcher)
    }

    // Notify the observer to update the view
    notify() {
        // console.log(' informed observer ');
        this.subs.forEach(w= > w.update())
    }
}
Copy the code

The Watcher subscriber

class Watcher {
    constructor(vm, expr, cb) {
        this.vm = vm
        this.expr = expr
        this.cb = cb
        // Save the old values first
        this.oldVal = this.getOldVal()
    }

    getOldVal() {
        Dep.target = this
        const oldVal = compileUtil.getValue(this.expr, this.vm)
        Dep.target = null
        return oldVal
    }

    update() {
        const newVal = compileUtil.getValue(this.expr, this.vm)
        this.cb(newVal)
    }
}

Copy the code

Partially referenced from (www.cnblogs.com/it-xiong/p/…)