The target

Master the basic principle of Vue2 framework

The main points of

I don’t want to talk too much. Just disk it

Let’s start with an HTML file

<div id="app">
    <p>{{counter}}</p>
    <p v-text="counter"></p>
</div>

<script src="./MyVue.js"></script>
<script>
    const app = new XuSirVue({
        el: '#app'.data: {
           counter: 1}})setInterval(() = > {
        app.counter++
    }, 1000)
</script>
Copy the code

The key came, I darling, first look at the MVVM classic schematic 😎😎😎

Let’s start with a response form, from the shallow to the deep, continue to look down at 缟

function defineReactive(obj, key, val) {
    // Recursive processing, to the object deep interception
    observe(val)
    // Create a Dep instance
    const dep = new Dep()
    Object.defineProperty(obj, key, {
        get() {
            // Check whether dep. target exists and collect dependencies if it does
            // Dep.target = watcher
            Dep.target && dep.addDep(Dep.target);
            return val
        },

        set(v) {
            if(v ! == val) { val = v }// DeP managed Watchers is notified to update when a value change is detected
            // this.deps.forEach(dep => dep.update());
            Call (this.vm, this.vm[this.key]); // Watcher executes the update function this.updatefn. call(this.vm, this.vm[this.key]); Correspond to the following consents
            dep.notify()
        }
    })
}

defineReactive
function observe(obj) {
    // First determine if obj is an object, ignoring my questionable notation 😂
    if (typeofobj ! = ='object' || obj === null) {
        return
    }
    new Observer(obj) // Observer
}
Copy the code

Get an observer to listen for changes in data and make it a responsive streak

// Observer: listen for incoming data --> become responsive data
class Observer {
    constructor(obj) {
        this.value = obj
        if (Array.isArray(obj)) {
            / / todo array rewrite the seven methods of short duration does not handle push/shift/pop/unshift...
        } else {
            this.work(obj)
        }
    }
    // Add intercepts to objects to make them responsive
    work(obj) {
        Object.keys(obj).forEach(key= > {
            defineReactive(obj, key, obj[key])
        })
    }
}
Copy the code

Insert a proxy — put the key of data on the VM. We can write this. XXX to get variables in data directly without writing this.data

// Exception handling is ignored -- for example, if the VM already has a $data[key], we cannot override the original attribute key
function proxy(vm) {
    Object.keys(vm.$data).forEach(key= > {
        Object.defineProperty(vm, key, {
            get() {
                return vm.$data[key];
            },
            set(newVal){ vm.$data[key] = newVal; }}); })}Copy the code

Vue main entrance came to 缟

class XuSirVue {
    constructor(options) {
        // 1
        this.$options = options
        // bind data to $data
        this.$data = options.data
        observe(this.$data) // Start viewing all data attributes

        // proxy - pass vm to proxy method for processing
        proxy(this)
        
        / / 2. com running the compilation
        // compile {{}} variables, v-xx instructions into the corresponding values and methods on the binding
        new Compile(options.el, this)}}Copy the code

Get a compiler – a bit long but very clear comments, ha ha 😂 缟

class Compile {
    constructor(el, vm) {
        this.$vm = vm;
        this.$el = document.querySelector(el);
        / / traverse el
        if (this.$el) {
            this.compile(this.$el); }}compile(el) {
        const childNodes = el.childNodes;
        // childNodes is a pseudo-array
        Array.from(childNodes).forEach(node= > {
            if (this.isElement(node)) {
                console.log("Compile element" + node.nodeName);
                this.compileElement(node)
            } else if (this.isInterpolation(node)) {
                console.log("Compile interpolated text V-text" + node.textContent);
                this.compileText(node);
            }
            if (node.childNodes && node.childNodes.length > 0) {
                this.compile(node); }}); }/ / element
    isElement(node) {
        return node.nodeType == 1;
    }

    // Is a {{}} interpolation
    isInterpolation(node) {
        return node.nodeType == 3 && / \ {\ {(. *) \} \} /.test(node.textContent);
    }

    // use RegExp.$1 to get the interpolation property XXX in {{XXX}}
    compileText(node) {
        console.log(RegExp. $1);// node.textContent = this.$vm[RegExp.$1];
        this.update(node, RegExp. $1,'text')}// Get the instruction expression v-xxx
    isDirective(attr) {
        return attr.indexOf("v-") = =0;
    }

    // Compile the element
    compileElement(node) {
        let nodeAttrs = node.attributes;
        // Compile the attribute v-if v-xxx on the element
        Array.from(nodeAttrs).forEach(attr= > {
            let attrName = attr.name;
            let exp = attr.value;
            if (this.isDirective(attrName)) {
                let dir = attrName.substring(2);
                this[dir] && this[dir](node, exp); }}); }// v-text
    text(node, exp) {
        this.update(node, exp, 'text')}// v-html
    html(node, exp) {
        this.update(node, exp, 'html')}// Unified processing instruction v-
    // dir: text exp: {{XXX}} XXX node: the element node that uses the instruction
    update(node, exp, dir) {
        const fn = this[dir + 'Updater']
        fn && fn(node, this.$vm[exp])
        // Triggers a view update
        new Watcher(this.$vm, exp, function (val) {
            fn && fn(node, val)
        })
    }

    // Handle node assignments for V-text
    textUpdater(node, val) {
        node.textContent = val;
    }
    // Handle v-HTML node assignments
    htmlUpdater(node, val) {
        node.innerHTML = val
    }
}
Copy the code

Look tired? Come to a bottle of Red Bull to continue to plate it

Dep is used to rely on collecting attributes in the main listening data. One attribute corresponds to a DEP that listens for changes and updates consents through watcher observers

class Dep {
    constructor() {
        // Depend on the collection
        this.deps = []
    }
    // Collect all dependencies, one data for each DEP manager, when the getter in the intercepting method above fires
    addDep(dep) {
        this.deps.push(dep)
    }
    // A change in the value of the data property triggers the setter, notifying the view to update
    notify() {
        this.deps.forEach(dep= >dep.update()); }}Copy the code

When the watcher is created, the getter is triggered. When the watcher is initialized, the corresponding data[key] is rendered to the view. Later, the butler DEP intercepts the data[key] when the value changes and updates it in batches. {{initStatus}} v-text=”initStatus”, initStatus corresponds to one DEP manager and two Watcher, InitStatus changes the notify method that triggers the DEP by watcher performing update to finally make the page view update 💘 缟

class Watcher {
    constructor(vm, key, updateFn) {
        this.vm = vm;
        / / rely on the key
        this.key = key;
        // Update function
        this.updateFn = updateFn;

        // Get the key to trigger get and create a mapping between the current Watcher instance and deP
        Dep.target = this;
        // The purpose of this step is to initialize the value to trigger the getter
        this.vm[this.key];
        Dep.target = null;
    }

    / / update
    update() {
        this.updateFn.call(this.vm, this.vm[this.key]); }}Copy the code

See the result 😇🤓🤡

supplement

✔ If you find the above code too messy, go to my Github for the full version