vue--MVVM

229 阅读3分钟

vue基本知识MVVM

MVVM 是vue的一大亮点,是学习vue必不可少的知识点,面试官们也钟情于此,手写简单的vueMVVM,笔记思路记录一下。

MVVM 设计模式,是由 MVC(最早来源于后端)、MVP 等设计模式进化而来,M - 数据模型(Model),VM - 视图模型(ViewModel),V - 视图层(View)。

在 MVC 模式中,除了 Model 和 View 层以外,其他所有的逻辑都在 Controller 中,Controller 负责显示页面、响应用户操作、网络请求及与 Model 的交互,随着业务的增加和产品的迭代,Controller 中的处理逻辑越来越多、越来越复杂,难以维护。为了更好的管理代码,为了更方便的扩展业务,必须要为 Controller “瘦身”,需要更清晰的将用户界面(UI)开发从应用程序的业务逻辑与行为中分离,MVVM 为此而生。

基于下图思路:

Compile:编译模板,如图将DOM树遍历元素节点和文本节点分开处理;

Observer:数据劫持,数据状态化抽象化,使之可响应;

Watcher:观察者,监测数据等的变化;

Dep:发布订阅,存储观察者;


一步步来~~~ 先写个简单的HTML

大体如下,自己补全啊,引入我们自己写的vueMVVM.js

//  简单HTML
<div id="app">
    <input type="text" v-model="school.name">
    <div>{{school.name}}</div>
    <div>{{school.age}}</div>
    {{getNewName}}
    <ul>
        <li>1</li>
        <li>1</li>
    </ul>
</div>
<!-- <script src="https://cdn.bootcss.com/vue/2.6.10/vue.common.dev.js"></script> -->
<script src="./vueMVVM.js"></script>

<script>
    let vm = new Vue({
        el:"#app",
        data:{
            school:{
                name:"beida",
                age:100
            }
        },
        methods: {
        },
        computed: {
            getNewName(){
                return this.school.name + "666";
            }
        }
    })
</script>

Compile编译模板

将DOM树遍历,节点化为文档碎片处理,元素节点和文本碎片分开处理

// 编译模板
class Compiler {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        this.vm = vm;
        // 文档碎片,所有节点
        let fragment = this.node2fragment(this.el);
        // 在内存中编译所有节点
        this.compile(fragment);
        // 把编译好的节点重新渲染到页面
        this.el.appendChild(fragment)
    }

    // 判断是否是指令
    isDirective(attrName) {
        return attrName.startsWith("v-")
    }

    // 编译元素节点
    compileElement(node) {
        let attributes = node.attributes;
        [...attributes].forEach(attr => {
            let {name,value: expr} = attr;
            if (this.isDirective(name)) {
                let [, directive] = name.split("-");
                CompilerUtil[directive](node, expr, this.vm);
            }
        })
    }

    // 编译文本节点
    compileText(node) {
        let content = node.textContent;
        let reg = /\{\{(.+?)\}\}/;
        reg.test(content)
        if (reg.test(content)) {
            CompilerUtil['text'](node, content, this.vm)
        }
    }

    // 编译
    compile(node) {
        let childNodes = node.childNodes;
        // 遍历判断
        [...childNodes].forEach(child => {
            if (this.isElementNode(child)) {
                // 是元素节点
                this.compileElement(child)
                // 元素节点中嵌套其他的元素节点或文本节点,回调
                this.compile(child)
            } else {
                // 是文本节点
                this.compileText(child)
            }
        })
    }
    // 文档碎片
    node2fragment(node) {
        let fragment = document.createDocumentFragment();
        let firstChild;
        while (firstChild = node.firstChild) {
            fragment.appendChild(firstChild)
        }
        return fragment
    }
    isElementNode(node) {
        return node.nodeType === 1;
    }
}

Observer数据劫持

数据劫持,就是给对象添加get、set方法,增加响应式

// 实现数据响应式
class Observer {
    constructor(data) {
        this.observer(data)
    }
    observer(data) {
        if (data && typeof data == 'object') {
            for (let key in data) {
                this.defindReactive(data, key, data[key])
            }
        }
    }
    defindReactive(obj, key, value) {
        this.observer(value);
        let dep = new Dep();
        Object.defineProperty(obj, key, {
            get() {
                Dep.target && dep.subs.push(Dep.target)
                return value
            },
            set: (newVal) => {
                if (newVal != value) {
                    this.observer(newVal)
                    value = newVal
                    dep.notify();
                }
            }
        })
    }
}

Watcher 观察者

存储了旧的值,当数据状态发生改变时,回调cb方法

// 观察者
class Watcher {
    constructor(vm, expr, cb) {
        this.vm = vm;
        this.expr = expr;
        this.cb = cb;
        this.oldValue = this.get();
    }
    // 获取状态
    get() {
        Dep.target = this;
        let value = CompilerUtil.getVal(this.vm, this.expr);
        Dep.target = null;
        return value;
    }
    // 状态改变时调用
    update() {
        let newVal = CompilerUtil.getVal(this.vm, this.expr);
        if (newVal !== this.oldValue) {
            this.cb(newVal);
        }
    }
}

Dep发布订阅

// 存储观察者
class Dep {
    constructor() {
        this.subs = [];
    }
    // 添加watcher
    <!-- 虽然没有什么用,数据劫持的时候就做了 -->
    addSub(watcher) {
        this.subs.push(watcher)
    }
    // 通知容器中的所有观察者
    notify() {
        this.subs.forEach(watcher => watcher.update())
    }
}

验证MVVM

还需要补充一点代码

CompilerUtil包括处理指令等方法的对象 和必要的Vue类

// 包含处理指令方法的对象
CompilerUtil = {
    getVal(vm, expr) {
        return expr.split(".").reduce((data, current) => {
            return data[current]
        }, vm.$data)
    },
    setVal(vm, expr, value) {
        expr.split(".").reduce((data, current, index, arr) => {
            if (index == arr.length - 1) {
                return data[current] = value
            }
            return data[current]
        }, vm.$data)
    },
    model(node, expr, vm) {
        let fn = this.updater["modelUpdater"]
        new Watcher(vm, expr, (newVal) => {
            fn(node, newVal)
        })
        node.addEventListener("input", (e) => {
            let value = e.target.value
            console.log(value)
            this.setVal(vm, expr, value);
        })
        let value = this.getVal(vm, expr)
        fn(node, value);
    },
    html() {

    },
    getContentValue(vm, expr) {
        return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            return this.getVal(vm, args[1])
        })
    },
    text(node, expr, vm) {
        let fn = this.updater["textUpdater"]
        let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            new Watcher(vm, args[1], () => {
                fn(node, this.getContentValue(vm, expr))
            })
            return this.getVal(vm, args[1])
        })
        fn(node, content);
    },
    updater: {
        modelUpdater(node, value) {
            node.value = value
        },
        htmlUpdater() {},
        textUpdater(node, value) {
            node.textContent = value
        }
    }
}
// Vue类
class Vue {
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        let computed = options.computed;
        if (this.$el) {
            new Observer(this.$data)
            for (let key in computed) {
                Object.defineProperty(this.$data, key, {
                    get: () => {
                        return computed[key].call(this)
                    }
                })
            }
            this.proxyVm(this.$data)
            new Compiler(this.$el, this)
        }
    }
    //  代理data
    proxyVm(data) {
        for (let key in data) {
            Object.defineProperty(this, key, {
                get() {
                    return data[key]
                }
            })
        }
    }
}