mvvm oop 实现

原文链接: mvvm oop 实现

上一篇: mvvm vue 实现原理 介绍

下一篇: js 继承

实现和vue类似的效果

项目结构

27585a8799c0cd64f7c845e8c88e18174ae.jpg

compile.js

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

        if (this.$el) {
            // 将绑定的dom实例的所有层次子节点保存为fragment
            this.$fragment = this.node2Fragment(this.$el);
            // 编译fragment中所有层次子节点
            this.init();
            // 将编译好的fragment添加到挂载的el元素上
            this.$el.appendChild(this.$fragment);
        }
    }

    node2Fragment(el) {
        let fragment = document.createDocumentFragment()
        let child;
        // 将原生dom节点拷贝到fragment
        while (child = el.firstChild) {
            fragment.appendChild(child);
        }

        return fragment;
    }

    init() {
        this.compileElement(this.$fragment);
    }

    // 编译改节点下的所有层次子节点
    compileElement(el) {

        Array.from(el.childNodes).forEach((node) => {
            let text = node.textContent;
            // 提取表达式 {{msg}}
            let reg = /\{\{(.*)\}\}/;

            if (this.isElementNode(node)) {
                // 如果是元素结点,递归继续编译
                // 编译属性指令
                this.compile(node);
            } else if (this.isTextNode(node) && reg.test(text)) {
                // 如果是使用了{{msg}}形式的 的文本结点
                // 将表达式转化为普通字符串
                this.compileText(node, RegExp.$1.trim());
            }

            // 如果该结点下还有其他元素标签
            if (node.childNodes && node.childNodes.length) {
                this.compileElement(node);
            }
        });
    }

    compile(node) {
        let nodeAttrs = node.attributes
        Array.from(nodeAttrs).forEach(attr => {
            let attrName = attr.name;
            if (this.isDirective(attrName)) {
                let exp = attr.value;
                //  v-on --> on dir 为指令类型
                let dir = attrName.substring(2);
                if (this.isEventDirective(dir)) {
                    // 事件指令
                    compileUtil.eventHandler(node, this.$vm, exp, dir);
                } else {
                    // 普通指令
                    compileUtil[dir] && compileUtil[dir](node, this.$vm, exp);
                }

                // 移除属性值,在渲染时不显示
                node.removeAttribute(attrName);
            }
        });
    }

    compileText(node, exp) {
        compileUtil.text(node, this.$vm, exp);
    }

    isDirective(attr) {
        return attr.indexOf('v-') == 0;
    }

    isEventDirective(dir) {
        return dir.indexOf('on') === 0;
    }

    isElementNode(node) {
        return node.nodeType == 1;
    }

    isTextNode(node) {
        return node.nodeType == 3;
    }
};

// 指令处理集合
const compileUtil = {
    text: function (node, vm, exp) {
        this.bind(node, vm, exp, 'text');
    },

    html: function (node, vm, exp) {
        this.bind(node, vm, exp, 'html');
    },

    model: function (node, vm, exp) {
        // v-model  数据双向绑定
        // 先将数据设置到input的value中
        this.bind(node, vm, exp, 'model');
        //  得到初始值
        let val = this._getVMVal(vm, exp);

        // 使用闭包,在监听事件的回调函数中,如果值有更新
        // 则重新将值更新到input的value中
        node.addEventListener('input', (e) => {
            let newValue = e.target.value;
            if (val === newValue) {
                return;
            }
            this._setVMVal(vm, exp, newValue);
            val = newValue;
        });
    },

    class: function (node, vm, exp) {
        this.bind(node, vm, exp, 'class');
    },

    bind: function (node, vm, exp, dir) {
        let updaterFn = updater[dir + 'Updater'];
        console.log('bind')
        updaterFn && updaterFn(node, this._getVMVal(vm, exp));

        // 表达式来源有大括号表达式和指令表达式
        new Watcher(vm, exp, function (value, oldValue) {
            updaterFn && updaterFn(node, value, oldValue);
        });
    },

    // 事件处理
    eventHandler: function (node, vm, exp, dir) {
        let eventType = dir.split(':')[1]
        let fn = vm.$options.methods && vm.$options.methods[exp];
        if (eventType && fn) {
            // 回调函数使用bind强制绑定this
            node.addEventListener(eventType, fn.bind(vm), false);
        }
    },

    _getVMVal: function (vm, exp) {
        let val = vm;
        exp = exp.split('.');
        exp.forEach(function (k) {
            val = val[k];
        });
        return val;
    },

    _setVMVal: function (vm, exp, value) {
        let val = vm;
        exp = exp.split('.');
        exp.forEach(function (k, i) {
            if (i < exp.length - 1) {
                // 非最后一个key,更新val的值
                val = val[k];
            } else {
                // 最后一个key, 赋新值
                val[k] = value;
            }
        });
    }
};

//  包含多个更新节点的方法工具对象
// 执行node的更新
const updater = {
    textUpdater: function (node, value) {
        node.textContent = typeof value == 'undefined' ? '' : value;
    },

    htmlUpdater: function (node, value) {
        node.innerHTML = typeof value == 'undefined' ? '' : value;
    },

    classUpdater: function (node, value, oldValue) {
        let className = node.className;
        console.log(className, node, value, oldValue)
        // 'a b' value='c' oldval = 'a b'
        // 替换后 '  c' ==> 'c'
        className = className.replace(oldValue, '').replace(/\s$/, '');
        // 如果有类名用空格连接
        console.log(className)
        let space = className && String(value) ? ' ' : '';
        node.className = className + space + value;
    },

    modelUpdater: function (node, value, oldValue) {
        node.value = typeof value == 'undefined' ? '' : value;
    }
};

observer.js

class Observer {
    constructor(data) {
        this.data = data;
        this.walk(data);
    }

    walk(data) {
        Object.keys(data).forEach((key) => {
            this.convert(key, data[key]);
        });
    }

    convert(key, val) {
        this.defineReactive(this.data, key, val);
    }

    defineReactive(data, key, val) {
        let dep = new Dep();
        // let childObj = observe(val);
        observe(val);

        // 使用闭包完成关系绑定
        Object.defineProperty(data, key, {
            enumerable: true, // 可枚举
            configurable: false, // 不能再define
            get: function () {
                if (Dep.target) {
                    //   depend() {
                    //         Dep.target.addDep(this);
                    //     }
                    // 当key被使用的时候,如果target有值(是一个watcher)
                    // 则该target需要依赖此dep,且dep的subs数组中加入watcher
                    //    addDep(dep) {
                    //         if (!this.depIds.hasOwnProperty(dep.id)) {
                    //             dep.addSub(this);
                    //             this.depIds[dep.id] = dep;
                    //         }
                    //     }
                    dep.depend();
                }
                return val;
            },
            set: function (newVal) {
                // 使用闭包,完成更新
                if (newVal === val) {
                    return;
                }
                val = newVal;
                // 新的值是object的话,进行监听
                observe(newVal);

                // 通知订阅者,执行订阅者的update方法,更新node中的数据
                dep.notify();
            }
        });
    }
};

function observe(value) {
    if (!value || typeof value !== 'object') {
        // 递归结束条件,value为空或者不是对象
        return;
    }

    new Observer(value);
};

// 标示dep的唯一id,自增
let uid = 0;

class Dep {
    constructor() {
        this.id = uid++;
        this.subs = [];
        console.log(uid)
    }

    addSub(sub) {
        this.subs.push(sub);
    }

    depend() {
        Dep.target.addDep(this);
    }

    removeSub(sub) {
        let index = this.subs.indexOf(sub);
        if (index != -1) {
            this.subs.splice(index, 1);
        }
    }

    notify() {
        // 通知所有订阅者更新
        this.subs.forEach((sub) => {
            sub.update();
        });
    }
};

// 借助全局变量做了两个类之间的参数传递和数据通信
Dep.target = null;

watcher.js

class Watcher {
    constructor(vm, expOrFn, cb) {
        // watcher 会在一开始初始化一个实例
        // 然后每次有一个表达式或者属性指令,都会再次实例化一个
        console.log('watcher init')
        this.cb = cb;
        this.vm = vm;
        this.expOrFn = expOrFn;
        // 如果 a.b.c
        // 则depIds 就有三个uid
        this.depIds = {};
        if (typeof expOrFn === 'function') {
            this.getter = expOrFn;
        } else {
            this.getter = this.parseGetter(expOrFn.trim());
        }
        this.value = this.get();
    }

    update() {
        this.run();
    }

    run() {
        // 新的值
        let value = this.get();
        // 旧值
        let oldVal = this.value;
        if (value !== oldVal) {
            // 更新
            this.value = value;
            // 绑定this,并且将新值和旧值传递给回调函数
            this.cb.call(this.vm, value, oldVal);
        }
    }

    addDep(dep) {
        // 1. 每次调用run()的时候会触发相应属性的getter
        // getter里面会触发dep.depend(),继而触发这里的addDep
        // 2. 假如相应属性的dep.id已经在当前watcher的depIds里,说明不是一个新的属性,仅仅是改变了其值而已
        // 则不需要将当前watcher添加到该属性的dep里
        // 3. 假如相应属性是新的属性,则将当前watcher添加到新属性的dep里
        // 如通过 vm.child = {name: 'a'} 改变了 child.name 的值,child.name 就是个新属性
        // 则需要将当前watcher(child.name)加入到新的 child.name 的dep里
        // 因为此时 child.name 是个新值,之前的 setter、dep 都已经失效,如果不把 watcher 加入到新的 child.name 的dep中
        // 通过 child.name = xxx 赋值的时候,对应的 watcher 就收不到通知,等于失效了
        // 4. 每个子属性的watcher在添加到子属性的dep的同时,也会添加到父属性的dep
        // 监听子属性的同时监听父属性的变更,这样,父属性改变时,子属性的watcher也能收到通知进行update
        // 这一步是在 this.get() --> this.getVMVal() 里面完成,forEach时会从父级开始取值,间接调用了它的getter
        // 触发了addDep(), 在整个forEach过程,当前watcher都会加入到每个父级过程属性的dep
        // 例如:当前watcher的是'child.child.name', 那么child, child.child, child.child.name这三个属性的dep都会加入当前watcher
        if (!this.depIds.hasOwnProperty(dep.id)) {
            dep.addSub(this);
            this.depIds[dep.id] = dep;
        }
    }

    get() {
        Dep.target = this;
        // 第一个参数绑定this, 第二个参数会传递给准备执行的函数
        // 设置全局Dep.target 调用get时, 借助闭包建立关系
        let value = this.getter.call(this.vm, this.vm);
        Dep.target = null;
        return value;
    }

    parseGetter(exp) {
        // 类似于 a. 的不合法表达式
        if (/[^\w.$]/.test(exp)) return;
        let exps = exp.split('.');

        // 实现 类似 a.b.c 的监听
        return function (obj) {
            for (let i = 0, len = exps.length; i < len; i++) {
                if (!obj) return;
                obj = obj[exps[i]];
            }
            return obj;
        }
    }
};

mvvm.js

class MVVM {
    constructor(options) {
        // 保存配置,防止使用空对象
        this.$options = options || {};
        // _data 用于保存真实数据
        let data = this._data = this.$options.data;

        // 数据代理
        // 实现 vm.xxx 代理 vm._data.xxx
        Object.keys(data).forEach((key) => {
            this._proxyData(key);
        });

        this._initComputed();

        observe(data, this);

        this.$compile = new Compile(options.el || document.body, this)
    }

    $watch(key, cb, options) {
        new Watcher(this, key, cb);
    }

    _proxyData(key, setter, getter) {
        setter = setter ||
            Object.defineProperty(this, key, {
                configurable: false, // 不可再重新定义变量
                enumerable: true, // 可枚举
                get: () => {
                    return this._data[key];
                },
                set: (newVal) => {
                    this._data[key] = newVal;
                }
            });
    }

    _initComputed() {
        let computed = this.$options.computed;
        if (typeof computed === 'object') {
            Object.keys(computed).forEach((key) => {
                Object.defineProperty(this, key, {
                    // 计算属性的写法有两种
                    // 一种是函数式,一种是对象式
                    get: typeof computed[key] === 'function'
                        ? computed[key]
                        : computed[key].get,
                    set: function () {
                    }
                });
            });
        }
    }

}


html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>MVVM</title>
    <style>
        .box {
            background: deepskyblue;
        }

        .font {
            color: blue;
        }
    </style>
</head>
<body>

<div id="mvvm-app">
    <input type="text" v-model="someStr">
    <input type="text" v-model="child.someStr">
    <h1 v-text="child.someStr"></h1>
    <h1 v-text="child.someStr"></h1>
    <h1>==={{ getHelloWord }}===</h1>
    <p v-html="htmlStr"></p>
    <button v-on:click="clickBtn">change model</button>

    <div class="box" v-class="cla">
        box
    </div>
    <div class="box" v-class="cla">
        box
    </div>
</div>

<script src="./js_oop/observer.js"></script>
<script src="./js_oop/watcher.js"></script>
<script src="./js_oop/compile.js"></script>
<script src="./js_oop/mvvm.js"></script>
<script>
    var vm = new MVVM({
        el: '#mvvm-app',
        data: {
            cla: 'font',
            someStr: 'hello ',
            className: 'btn',
            htmlStr: '<span style="color: #f00;">red</span>',
            child: {
                someStr: 'World !',
                text: 'World !',
            }
        },

        computed: {
            getHelloWord: function () {
                return this.someStr + this.child.someStr;
            }
        },

        methods: {
            clickBtn: function (e) {
                let randomStrArr = ['childOne', 'childTwo', 'childThree'];
                this.child.someStr = randomStrArr[parseInt(Math.random() * 3)];
            }
        }
    });

    vm.$watch('child.someStr', function (val, oldVal) {
        console.log('child.someStr', val, '===', oldVal);
    });
</script>

</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值