首页 > 其他 > 详细

MVVM相应式原理

时间:2020-05-08 15:05:16      阅读:93      评论:0      收藏:0      [点我收藏+]

vue是采用数据劫持配合发布者-订阅者模式的方式,通过Obejct.definerProperty()来劫持各个属性的setter和getter,在数据变动时,发布消息给依赖收集器(Dep),去通知观察者(Watcher),做出对应的回调函数,去更新视图。

MVVM作为绑定入口,整合Observer和Compile和Watcher三者,通过Observer来监听model数据变化,通过Compile来解析模板指令,最终利用Watcher搭起Observer,Compile之间的通信桥梁,达到数据变化 ==>视图更新,视图交互变化 ==>数据model变更的双向绑定效果。

 

实现MVVM原理(参考与b站up视频教学)

Vue.js

/**
 * options: 传进来的参数
 */
class MVue {
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        this.$options = options;

        if (this.$el) {
            //观察者
            new Observer(this.$data)

            //解析器
            new Compile(this.$el, this)

            this.proxyData(this.$data)
        }
    }
    proxyData(data) {
        for (const key in data) {
            Object.defineProperty(this, key, {
                get() {
                    return data[key]
                },
                set(newVal) {
                    data[key] = newVal
                }
            })
        }
    }
}

Watcher.js

class Watcher{
    constructor(vm, expr, cb) {
        this.vm = vm;
        this.expr = expr;
        this.cb = cb;
        this.oldVal = this.getOldVal();
    }
    getOldVal() {
        // 把watcher挂在到dep上
        Dep.target = this;
        const oldVal = compileUtil.getVal(this.expr, this.vm);
        Dep.target = null;
        return oldVal
    }
    update() {
        const newVal = compileUtil.getVal(this.expr, this.vm);
        if (newVal !== this.oldVal) {
            this.cb(newVal)
        }
    }
}

Observer.js

class Observer{
    constructor(data) {
        this.observe(data)
    }
    observe(data) {
        if (data && typeof data === ‘object‘) {
            Object.keys(data).forEach(key => {
                this.defineReactive(data,key,data[key])
            })
        }
    }
    defineReactive(obj, key, value) {
        this.observe(value);
        const dep = new Dep();

        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: false,
            get() {
                //订阅数据变化,当dep中添加观察者
                Dep.target && dep.addSub(Dep.target)
                return value;
            },
            set: (newVal) => {
                if (newVal !== value) {
                    value = newVal
                }

                //数据改变通知视图改变
                dep.notify()
            }
        })
    }
}

Dep.js

class Dep{
    constructor() {
        this.subs =[]
    }
    //收集观察者
    addSub(watcher) {
        this.subs.push(watcher)
    }
    //通知观察者更新
    notify() {
        this.subs.forEach(w =>w.update())
    }

}

Compile.js

const compileUtil = {
    getVal(expr, vm) {
        return expr.split(".").reduce((data, currentVal) => {
            // console.log(data[currentVal])
            return data[currentVal]
        }, vm.$data)
    },
    getContentVal(expr,vm) {
        return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            return this.getVal(args[1],vm)
        })
    },
    setVal(expr, vm, val) {
        return expr.split(".").reduce((data, currentVal) => {
            data[currentVal] = val;
      },vm.$data)  
    },
    text(node, expr, vm) {
        let value;
        if (expr.indexOf("{{") !== -1) {
            value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
                new Watcher(vm, args[1], (newVal) => {
                    this.updater.textUpdater(node,this.getContentVal(expr,vm))
                })
                return this.getVal(args[1], vm)
            })
        } else {
            value = this.getVal(expr, vm)
        }

        this.updater.textUpdater(node, value)
    },
    html(node, expr, vm) {
        const value = this.getVal(expr, vm);
        new Watcher(vm, expr, (newVal) => {
            this.updater.htmlUpdater(node,newVal)
        })
        this.updater.htmlUpdater(node, value)
    },
    model(node, expr, vm) {
        const value = this.getVal(expr, vm);
        //绑定更新函数, 数据=>视图
        new Watcher(vm, expr, (newVal) => {
            this.updater.modelUpdater(node,newVal)
        })
        //视图 => 数据 =>视图
        node.addEventListener("input", (e) => {
            this.setVal(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)
    },
    bind(node, expr, vm,eventName) {
        const value = this.getVal(expr, vm);
        new Watcher(vm, expr, (newVal) => {
            this.updater.bindUpdater(node,newVal,eventName)
        })
        this.updater.bindUpdater(node,value,eventName)
    },
    /**
     * node: 节点
     * value: $data所对应的值
     * eventName:属性名
     */
    updater: {
        textUpdater(node, value) {
            node.textContent = value;
        },
        htmlUpdater(node, value) {
            node.innerHTML = value
        },
        modelUpdater(node, value) {
            node.value = value
        },
        bindUpdater(node,value,eventName) {
            node.setAttribute(eventName, value)
        }
    }
}

class Compile {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        this.vm = vm;

        //获取文档碎片对象
        const fragment = this.node2Fragment(this.el);
        // console.log(fragment)

        //编译模板
        this.compile(fragment)

        //添加到跟元素
        this.el.appendChild(fragment)
    }
    /**
     * 
     * @param {*} fragment 进行模板的编译
     */
    compile(fragment) {
        const childNodes = fragment.childNodes;
        [...childNodes].forEach(child => {
            if (this.isElementNode(child)) {
                //元素节点,编译元素节点
                this.compileElement(child)
            } else {
                //文本节点,编译文本节点
                this.compileText(child)
            }
            if (child.childNodes && child.childNodes.length) {
                this.compile(child)
            }
        })
    }
    /**
     * 
     * @param {*} node 编译元素节点
     */
    compileElement(node) {
        const attributes = node.attributes;
        [...attributes].forEach(attr => {
            const { name, value } = attr;
            if (this.isDirective(name)) {
                const [, dirctive] = name.split("-");
                const [dirName, eventName] = dirctive.split(":");

                //更新数据 数据驱动视图
                compileUtil[dirName](node, value, this.vm, eventName)

                //删除有指令的标签属性
                node.removeAttribute(‘v-‘ + dirctive)

            } else if (this.isEventName(name)) {
                let [, eventName] = name.split("@");
                compileUtil[‘on‘](node, value, this.vm, eventName)
            } else if (this.isBindName(name)) {
                let [, eventName] = name.split(":");
                compileUtil[‘bind‘](node, value, this.vm, eventName)
                //删除动态绑定属性, 如 :html=‘index‘
                node.removeAttribute(‘:‘+eventName)
            }

        })
    }
    isBindName(attrName) {
        return attrName.startsWith(":")
    }
    /**
     * 
     * @param {*} attrName 处理属性@
     */
    isEventName(attrName) {
        return attrName.startsWith("@")
    }
    /**
     * 
     * @param {*} attrName 处理属性名v-
     */
    isDirective(attrName) {
        return attrName.startsWith("v-")
    }
    /**
     * 
     * @param {*} node 编译文本节点
     */
    compileText(node) {
        const content = node.textContent;
        if (/\{\{(.+?)\}\}/.test(content)) {
            compileUtil[‘text‘](node, content, this.vm);
        }
    }

    /**
     * 
     * @param {*} el 获取当前元素下所有节点
     */
    node2Fragment(el) {
        const f = document.createDocumentFragment();
        let firstChild;
        while (firstChild = el.firstChild) {
            f.appendChild(firstChild)
        }
        return f;
    }

    /**
     * 
     * @param {*} node 判断是否时元素节点
     */
    isElementNode(node) {
        return node.nodeType === 1
    }
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <h1>123</h1>
        <h3 v-text="msg">456</h3>
        <h4 v-text="class.age"></h4>
        <p v-html="msg" :html="index"></p>
        <div>{{msg}}</div>
        <input type="text" v-model="msg">
        <div v-bind:data="msg">我时谁</div>
        <span :value="val">vue模拟MVVM属性绑定</span>
        <button @click="btn" :value="index">点击</button>
    </div>

    <script src="./mVue.js"></script>
    <script src="./Compile.js"></script>
    <script src="./Watcher.js"></script>
    <script src="./Dep.js"></script>
    <script src="./Observer.js"></script>

    <script>
        var vm = new MVue({
            el: "#app",
            data: {
                msg: ‘a‘,
                class: {
                    age: 12
                },
                name:"哈哈",
                index:123,
                val:"终于完成了"
            },
            methods: {
                btn() {
                    console.log(123)
                }
            }
        })
    </script>
</body>
</html>

 

 

 

MVVM相应式原理

原文:https://www.cnblogs.com/wangyisu/p/12850502.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!