Vue-MVVM原理

1.完整示例

1.1引入vue的cdn

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Vue 测试实例 - 菜鸟教程(runoob.com)</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
</head>

<body>
    <div id="app">
        <input type="text" v-model="message">
        <p>{{ message }}</p>
    </div>

    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                message: 'Hello Vue.js!'
            }
        })
    </script>
</body>

</html>

1.2视图影响数据

clipboard.png

1.3数据影响视图

clipboard.png

2.实现MVVM原理

2.1项目构架

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>

</body>

</html>
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Vue 测试实例 - 菜鸟教程(runoob.com)</title>
    <script src="./js/mvvm.js"></script>
    <script src="./js/compile.js"></script>
</head>

<body>
    <div id="app">
        <input type="text" v-model="message">
        <div>{{message}}</div>
        <ul>
            <li></li>
        </ul>
        {{message}}
    </div>

    <script>
        let vm = new MVVM({
            el: '#app',
            data: {
                message: 'Hello Vue.js!'
            }
        })
    </script>
</body>

</html>

3.mvvm.js

class MVVM {
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;

        if (this.$el) {
            new Compile(this.$el);
        }

    }
}

4.compile.js

4.1把dom节点,放在内存中操做

0:35html

class Compile {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        if (this.el) {
            let fragment = this.node2frament(this.el);
            this.compile(fragment);
        }
    }

    //辅助方法
    isElementNode(node) {
        return node.nodeType === 1;
    }

    //核心方法
    compile(fragment) {
        let childNodes = fragment.childNodes;
        console.log(childNodes)
    }
    node2frament(el) {
        let fragment = document.createDocumentFragment();
        let firstChild;
        while (firstChild = el.firstChild) {
            fragment.appendChild(firstChild);
        }
        return fragment
    }
}

4.1.1node2frament

node2frament中的el.firstChild
只能获取到#app的第一层子节点,<ul><li></li></ul>中的<li>获取不到vue

node2fragment(el) {
        //文档碎片,内存中的dom节点
        let fragment = document.createDocumentFragment();
        let firstChild;
        //    临界条件 firstChild = null
        while (firstChild = el.firstChild) {
            console.log(el.firstChild)
            fragment.appendChild(firstChild)
        }
        return fragment;
    }

clipboard.png

4.1.2fragment

node2fragment(el) {
        console.log(el)
            //文档碎片,内存中的dom节点
        let fragment = document.createDocumentFragment();
        let firstChild;
        //    临界条件 firstChild = null
        while (firstChild = el.firstChild) {
            fragment.appendChild(firstChild)
        }
        console.log(fragment)
        return fragment;
    }
console.log(fragment)
                this.compile(fragment);

clipboard.png

4.2分类元素节点和文本节点

0:52node

class Compile {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        if (this.el) {
            let fragment = this.node2frament(this.el);
            this.compile(fragment);
        }
    }

    //辅助方法
    isElementNode(node) {
        return node.nodeType === 1;
    }
    isDirective(name) {
        return name.includes('v-')
    }

    //核心方法
    compileElement(node) {
        let attrs = node.attributes;
        Array.from(attrs).forEach(arrt => {
            let attrName = attr.name;
            if (this.isDirective(attrName)) {
                let expr = attr.value;
            }
        })
    }
    compileText(node) {
        let text = node.textContent;
        let reg = /\{\{([^}]+)\}\}/g;
        if (reg.test(text)) {

        }
    }
    compile(fragment) {
        let childNodes = fragment.childNodes;
        Array.from(childNodes).forEach(node => {
            if (this.isElementNode(node)) {
                this.compile(node)
            } else {
                console.log('text', node)
            }
        })
    }
    node2frament(el) {
        let fragment = document.createDocumentFragment();
        let firstChild;
        while (firstChild = el.firstChild) {
            fragment.appendChild(firstChild);
        }
        return fragment
    }
}

4.2.1 compile()

4.2.1.1元素节点

compile(fragment) {
        let childNodes = fragment.childNodes;
        Array.from(childNodes).forEach(node => {
            if (this.isElementNode(node)) {
                console.log(node)
                this.compileElement(node)
                this.compile(node)
            } else {
                this.compileText(node)
            }
        })
    }

clipboard.png

4.2.1.1文本节点

compile(fragment) {
        let childNodes = fragment.childNodes;
        Array.from(childNodes).forEach(node => {
            if (this.isElementNode(node)) {
                this.compileElement(node)
                this.compile(node)
            } else {
                console.log(node)
                this.compileText(node)
            }
        })
    }

clipboard.png

4.2.2 compileText()

compileText(node) {
        let expr = node.textContent;
        let reg = /\{\{([^}]+)\}\}/g;
        if (reg.test(expr)) {
            console.log(expr)
        }
    }

clipboard.png

4.2.3 compileElement()

compileElement(node) {
        let attrs = node.attributes;
        Array.from(attrs).forEach(attr => {
            // console.log(attr)
            let attrName = attr.name;
            if (this.isDirective(attrName)) {
                let expr = attr.value;
                let [, type] = attrName.split('-');
                console.log(node)
                console.log(this.vm)
                console.log(expr)
                CompileUtil[type](node, this.vm, expr);
            }
        })
    }

clipboard.png

4.3把data中的数据,显示在视图上

4.3.1 完整代码 1:16app

class Compile {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        if (this.el) {
            let fragment = this.node2frament(this.el);
            this.compile(fragment);
            this.el.appendChild(fragment)
        }
    }

    //辅助方法
    isElementNode(node) {
        return node.nodeType === 1;
    }
    isDirective(name) {
        return name.includes('v-')
    }

    //核心方法
    compileElement(node) {
        let attrs = node.attributes;
        Array.from(attrs).forEach(attr => {
            let attrName = attr.name;
            if (this.isDirective(attrName)) {
                let expr = attr.value;
                let [, type] = attrName.split('-');
                CompileUtil[type](node, this.vm, expr)
            }
        })
    }
    compileText(node) {
        console.log(node)
        let expr = node.textContent;
        let reg = /\{\{([^}]+)\}\}/g;
        if (reg.test(expr)) {
            CompileUtil['text'](node, this.vm, expr)
        }
    }
    compile(fragment) {
        let childNodes = fragment.childNodes;
        Array.from(childNodes).forEach(node => {
            if (this.isElementNode(node)) {
                this.compileElement(node)
                this.compile(node)
            } else {
                this.compileText(node)
            }
        })
    }
    node2frament(el) {
        let fragment = document.createDocumentFragment();
        let firstChild;
        while (firstChild = el.firstChild) {
            fragment.appendChild(firstChild);
        }
        return fragment
    }
}


CompileUtil = {
    getVal(vm, expr) { // 获取实例上对应的数据
        expr = expr.split('.'); // [message,a]
        return expr.reduce((prev, next) => { // vm.$data.a
            return prev[next];
        }, vm.$data);
    },
    getTextVal(vm, expr) { // 获取编译文本后的结果
        return expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
            return this.getVal(vm, arguments[1]);
        })
    },
    text(node, vm, expr) { //文本处理
        let updateFn = this.updater['textUpdater'];
        let value = this.getTextVal(vm, expr);
        updateFn && updateFn(node, value)
    },

    model(node, vm, expr) {
        let updateFn = this.updater['modelUpdater'];
        updateFn && updateFn(node, this.getVal(vm, expr));
    },
    updater: {
        textUpdater(node, value) {
            node.textContent = value;
        },
        modelUpdater(node, value) {
            node.value = value;
        }
    }
}

4.3.2 v-model类型

4.3.2.1 getVal()

根据<input type="text" v-model="message.a"> 中的message.a,找到data.message.a中真实的数据,例如如今是hello world
原始dom

getVal(vm, expr) { // 获取实例上对应的数据
        console.log(vm)
        console.log(expr)
        expr = expr.split('.'); // [message,a]
        return expr.reduce((prev, next) => { // vm.$data.a
             return prev[next];
        }, vm.$data);
    },

方便看数据,改造mvvm

getVal(vm, expr) { // 获取实例上对应的数据
        console.log(vm)
        console.log(expr)
        expr = expr.split('.'); // [message,a]
        var result = expr.reduce((prev, next) => { // vm.$data.a
            return prev[next];
        }, vm.$data);
        console.log(result)
        return result;
    },

index.html测试

<body>
    <div id="app">
        <input type="text" v-model="message.a">
        <input type="text" v-model="b">
        <div>{{message.a}}</div>
        <ul>
            <li></li>
        </ul>
        {{message.a}}
    </div>

    <script>
        let vm = new MVVM({
            el: '#app',
            data: {
                message: {
                    a: "hello world"
                },
                b: 'bb'
            }
        })
    </script>
</body>

clipboard.png

4.3.2.2 model()

model(node, vm, expr) {
        console.log(this.getVal(vm, expr))
        let updateFn = this.updater['modelUpdater'];
        updateFn && updateFn(node, this.getVal(vm, expr));
    },

clipboard.png

4.3.2.3 updater.modelUpdater()

data.message.a的中数据(hello world),替换在对应的节点v-model="message.a"中的valueui

updater: {
        // 输入框更新
        modelUpdater(node, value) {
            console.log(node)
            console.log(value)
            node.value = value;
            console.log(node.value)
        },

    }

clipboard.png

4.4 完整代码compile.js

class Compile {
    constructor(el, vm) {
            this.el = this.isElementNode(el) ? el : document.querySelector(el);
            this.vm = vm;
            if (this.el) {
                // 1.先把这些真实的dom移入到内存中fragment
                let fragment = this.node2fragment(this.el);
                // 2.编译 => 提取想要的元素节点 v-model 和文本节点 {{}}
                this.compile(fragment);
                // 3.把编译好的fragment,再赛回到页面里去
                this.el.appendChild(fragment);
            }
        }
        /*
        辅助方法
        */

    //是不是元素节点
    isElementNode(node) {
        return node.nodeType === 1;
    }
    isDirective(name) {
            return name.includes('v-')
        }
        /*
        核心方法
        */
    node2fragment(el) {
        //文档碎片,内存中的dom节点
        let fragment = document.createDocumentFragment();
        let firstChild;
        //    临界条件 firstChild = null
        while (firstChild = el.firstChild) {
            fragment.appendChild(firstChild)
        }
        // console.log(fragment)
        return fragment;
    }
    compile(fragment) {
        let childNodes = fragment.childNodes;
        // console.log(childNodes)
        Array.from(childNodes).forEach(node => {
            if (this.isElementNode(node)) {
                // console.log(node)
                this.compileElement(node)
                this.compile(node)
            } else {
                // console.log(node)
                this.compileText(node)
            }
        })
    }
    compileElement(node) {
        let attrs = node.attributes;
        Array.from(attrs).forEach(attr => {
            // console.log(attr)
            let attrName = attr.name;
            if (this.isDirective(attrName)) {
                let expr = attr.value;
                let [, type] = attrName.split('-');
                // console.log(node)
                // console.log(this.vm)
                // console.log(expr)
                CompileUtil[type](node, this.vm, expr);
            }
        })
    }
    compileText(node) {
        let expr = node.textContent;
        let reg = /\{\{([^}]+)\}\}/g;
        if (reg.test(expr)) {
            // console.log(expr)
            // CompileUtil['text'](node, this.vm, expr)
        }
    }
}

CompileUtil = {
    getVal(vm, expr) { // 获取实例上对应的数据
        console.log(vm)
        console.log(expr)
        expr = expr.split('.'); // [message,a]
        // var result = expr.reduce((prev, next) => { // vm.$data.a
        //     return prev[next];
        // }, vm.$data);
        // console.log(result)
        // return result;
        return expr.reduce((prev, next) => { // vm.$data.a
            return prev[next];
        }, vm.$data);
    },
    getTextVal(vm, expr) {
        return expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
            return this.getVal(vm, arguments[1])
        })
    },
    text(node, vm, expr) {
        let updateFn = this.updater['textUpdater'];
        let value = this.getTextVal(vm, expr)
        updateFn && updateFn(node, value);
    },
    model(node, vm, expr) {
        // console.log(this.getVal(vm, expr))
        let updateFn = this.updater['modelUpdater'];
        updateFn && updateFn(node, this.getVal(vm, expr));
    },
    updater: {
        textUpdater(node, value) {
            node.textContent = value;
        },
        // 输入框更新
        modelUpdater(node, value) {
            // console.log(node)
            // console.log(value)
            node.value = value;
            // console.log(node.value)
        },

    }
}

5.observer.js 到1:36

5.1代码

class Observer {
    constructor(data) {
        this.observe(data)
    }
    observe(data) {
        if (!data || typeof data !== 'object') {
            return;
        }
        Object.keys(data).forEach(key => {
            this.defineReactive(data, key, data[key]);
            this.observe(data[key])
        })
    }
    defineReactive(obj, key, value) {
        let that = this;
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() {
                return value;
            },
            set(newValue) {
                if (newValue != value) {
                    that.observe(newValue)
                    value = newValue
                }
            }
        })
    }
}

5.2效果

clipboard.png

6.watcher 到1:56

6.1watcher.js

class Watcher {
    constructor(vm, expr, cb) {
        this.vm = vm;
        this.expr = expr;
        this.cb = cb;
        this.value = this.get();
    }
    getVal(vm, expr) {
        expr = expr.split('.');
        return expr.reduce((prev, next) => {
            return prev[next]
        }, vm.$data)
    }
    get() {
        let value = this.getVal(this.vm, this.expr);
        return value;
    }
    update() {
        let newValue = this.getVal(this.vm, this.expr);
        let oldValue = this.value;
        if (newValue != oldValue) {
            this.cb(newValue);
        }
    }
}

6.2修改compile.js

增长watcherthis

text(node, vm, expr) { //文本处理
        let updateFn = this.updater['textUpdater'];
        let value = this.getTextVal(vm, expr);
        expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
            return this.getVal(vm, arguments[1], (newValue) => {
                updateFn && updateFn(node, this.getTextVal(vm, expr));
            });
        })
        updateFn && updateFn(node, value)
    },

    model(node, vm, expr) {
        let updateFn = this.updater['modelUpdater'];
        new Watcher(vm, expr, (newValue) => {
            updateFn && updateFn(node, this.getVal(vm, expr));
        })
        updateFn && updateFn(node, this.getVal(vm, expr));
    },

完成的compile.js代码spa

class Compile {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        if (this.el) {
            let fragment = this.node2frament(this.el);
            this.compile(fragment);
            this.el.appendChild(fragment)
        }
    }

    //辅助方法
    isElementNode(node) {
        return node.nodeType === 1;
    }
    isDirective(name) {
        return name.includes('v-')
    }

    //核心方法
    compileElement(node) {
        let attrs = node.attributes;
        Array.from(attrs).forEach(attr => {
            let attrName = attr.name;
            if (this.isDirective(attrName)) {
                let expr = attr.value;
                let [, type] = attrName.split('-');
                CompileUtil[type](node, this.vm, expr)
            }
        })
    }
    compileText(node) {
        // console.log(node)
        let expr = node.textContent;
        let reg = /\{\{([^}]+)\}\}/g;
        if (reg.test(expr)) {
            CompileUtil['text'](node, this.vm, expr)
        }
    }
    compile(fragment) {
        let childNodes = fragment.childNodes;
        Array.from(childNodes).forEach(node => {
            if (this.isElementNode(node)) {
                // console.log(node)
                this.compileElement(node)
                this.compile(node)
            } else {
                // console.log(node)
                this.compileText(node)
            }
        })
    }
    node2frament(el) {
        let fragment = document.createDocumentFragment();
        let firstChild;
        while (firstChild = el.firstChild) {
            fragment.appendChild(firstChild);
        }
        return fragment
    }
}


CompileUtil = {
    getVal(vm, expr) { // 获取实例上对应的数据
        expr = expr.split('.'); // [message,a]
        return expr.reduce((prev, next) => { // vm.$data.a
            return prev[next];
        }, vm.$data);
    },
    getTextVal(vm, expr) { // 获取编译文本后的结果
        return expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
            return this.getVal(vm, arguments[1]);
        })
    },
    text(node, vm, expr) { //文本处理
        let updateFn = this.updater['textUpdater'];
        let value = this.getTextVal(vm, expr);
        expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
            return this.getVal(vm, arguments[1], (newValue) => {
                updateFn && updateFn(node, this.getTextVal(vm, expr));
            });
        })
        updateFn && updateFn(node, value)
    },

    model(node, vm, expr) {
        let updateFn = this.updater['modelUpdater'];
        new Watcher(vm, expr, (newValue) => {
            updateFn && updateFn(node, this.getVal(vm, expr));
        })
        updateFn && updateFn(node, this.getVal(vm, expr));
    },
    updater: {
        textUpdater(node, value) {
            node.textContent = value;
        },
        modelUpdater(node, value) {
            node.value = value;
        }
    }
}

6.3Dep 到2:05

修改observer.js

class Observer {
    constructor(data) {
        this.observe(data)
    }
    observe(data) {
        if (!data || typeof data !== 'object') {
            return;
        }
        Object.keys(data).forEach(key => {
            this.defineReactive(data, key, data[key]);
            this.observe(data[key])
        })
    }
    defineReactive(obj, key, value) {
        let that = this;
        let dep = new Dep();
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() {
                Dep.target && dep.addSub(Dep.target)
                return value;
            },
            set(newValue) {
                if (newValue != value) {
                    that.observe(newValue)
                    value = newValue
                    dep.notify();
                }
            }
        })
    }
}


class Dep {
    constructor() {
        this.subs = [];
    }
    addSub(watcher) {
        this.subs.push(watcher);
    }
    notify() {
        this.subs.forEach(watcher => watcher.update())
    }
}

7.数据变化,视图变化

8.input变化,视图变化

8.1compile.js

监听input,增长setval,到2:10

class Compile {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        if (this.el) {
            let fragment = this.node2frament(this.el);
            this.compile(fragment);
            this.el.appendChild(fragment)
        }
    }

    //辅助方法
    isElementNode(node) {
        return node.nodeType === 1;
    }
    isDirective(name) {
        return name.includes('v-')
    }

    //核心方法
    compileElement(node) {
        let attrs = node.attributes;
        Array.from(attrs).forEach(attr => {
            let attrName = attr.name;
            if (this.isDirective(attrName)) {
                let expr = attr.value;
                let [, type] = attrName.split('-');
                CompileUtil[type](node, this.vm, expr)
            }
        })
    }
    compileText(node) {
        // console.log(node)
        let expr = node.textContent;
        let reg = /\{\{([^}]+)\}\}/g;
        if (reg.test(expr)) {
            CompileUtil['text'](node, this.vm, expr)
        }
    }
    compile(fragment) {
        let childNodes = fragment.childNodes;
        Array.from(childNodes).forEach(node => {
            if (this.isElementNode(node)) {
                // console.log(node)
                this.compileElement(node)
                this.compile(node)
            } else {
                // console.log(node)
                this.compileText(node)
            }
        })
    }
    node2frament(el) {
        let fragment = document.createDocumentFragment();
        let firstChild;
        while (firstChild = el.firstChild) {
            fragment.appendChild(firstChild);
        }
        return fragment
    }
}


CompileUtil = {
    getVal(vm, expr) { // 获取实例上对应的数据
        expr = expr.split('.'); // [message,a]
        return expr.reduce((prev, next) => { // vm.$data.a
            return prev[next];
        }, vm.$data);
    },
    getTextVal(vm, expr) { // 获取编译文本后的结果
        return expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
            return this.getVal(vm, arguments[1]);
        })
    },
    setVal(vm, expr, value) { // [message,a]
        expr = expr.split('.');
        // 收敛
        return expr.reduce((prev, next, currentIndex) => {
            if (currentIndex === expr.length - 1) {
                return prev[next] = value;
            }
            return prev[next];
        }, vm.$data);
    },
    text(node, vm, expr) { //文本处理
        let updateFn = this.updater['textUpdater'];
        let value = this.getTextVal(vm, expr);
        expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
            new Watcher(vm, arguments[1], (newValue) => {
                updateFn && updateFn(node, this.getTextVal(vm, expr));
            });
        })
        updateFn && updateFn(node, value)
    },
    setVal(vm, expr, value) {
        expr = expr.split('.');
        return expr.reduce((prev, next, currentIndex) => {
            if (currentIndex === expr.length - 1) {
                return prev[next] = value;
            }
            return prev[next];
        }, vm.$data)
    },

    model(node, vm, expr) {
        let updateFn = this.updater['modelUpdater'];
        new Watcher(vm, expr, (newValue) => {
            updateFn && updateFn(node, this.getVal(vm, expr));
        })
        node.addEventListener('input', (e) => {
            let newValue = e.target.value;
            this.setVal(vm, expr, newValue)
        })
        updateFn && updateFn(node, this.getVal(vm, expr));
    },
    updater: {
        textUpdater(node, value) {
            node.textContent = value;
        },
        modelUpdater(node, value) {
            node.value = value;
        }
    }
}

9.完善

vm.$data.message => vm.message
mvvm.js增长proxyData

class MVVM {
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;

        if (this.$el) {
            new Compile(this.$el, this);
            this.proxydata(this.$data);
            new Observer(this.$data);
        }

    }

    proxyData(data) {
        Object.keys(data).forEach(key => {
            Object.defineProperty(this, key, {
                get() {
                    return data[key]
                },
                set(newValue) {
                    data[key] = newValue;
                }
            })
        })
    }
}

10. 节点

clipboard.png