简单实现vue的响应式和双向绑定

首先我们先大概讲一下思路 
我们需要5个类 可以理解成无人类 不同人拥有不同的技能
1. mvvm 用来整合 可以理解成boss
2. compiler 用来把数据编译到指定的元素
例如我们给一个input添加了v-model='person.name'属性 那么compiler要做的就是找到person.name对应的值
并把这个值赋给input的value
3. watcher 用来观察data中每一个属性 如果这个属性对应的属性值发生变化 就会重新渲染视图
4. dep 用来存储每个属性对应的所有watcher 如果属性对应的属性值发生变化 就通知watcher属性值发生了变化
5. observer 用来劫持data中所有的属性 让每一个属性都拥有一个dep 如果属性对应的属性值发生变化 就会让
dep通知这个属性对应的所有watcher watcher就会更新所有用到这个属性的视图
总结:
=> 数据发生变化 
=> observer监听到了 
=> 告诉这个属性对应的dep 
=> dep通知里面所有的watcher 
=> watcher完成视图更新
----------------------------------------------------------------------------
class Observer {
    constructor(data) {
        if (this.isObject(data)) {
            this.observer(data);
        } 
    }
    isObject(obj) {
        return ({}).toString.call(obj) === '[object Object]';
    }
    observer(data) {
        Object.keys(data).forEach(key => {
            if (this.isObject(data[key])) {
                this.observer(data[key]);
            }
            this.definedRective(data, key, data[key]);
        })
    }
    definedRective(obj, key, value) {
        let dep = new Dep();
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() {
                Dep.target && dep.addSubs(Dep.target);
                return value;
            },
            set(newValue) {
                if (value !== newValue) {
                    value = newValue;
                    dep.notify();
                }
            }
        })
    }
}
--------------------------------------------------------------------------
class Dep {
    constructor() {
        this.subs = [];
    }
    addSubs(watcher) {
        this.subs.push(watcher);
    }
    notify() {
        this.subs.forEach(watcher => watcher.update());
    }
}
-----------------------------------------------------------------------------------
class Watcher {
    constructor(data, expr, cb) {
        this.data = data;
        this.expr = expr;
        this.cb = cb;
        this.getValue();
    }
    getValue() {
        Dep.target = this;
        const value = this.expr.split('.').reduce((a, current) => a[current], this.data);
        Dep.target = null;
        return value;
    }
    update() {
        this.cb()
    }
}
------------------------------------------------------------------------------------------------------
class Compiler {
    constructor(el, data) {
        this.el = this.isElement(el) ? el : document.querySelector(el);
        this.data = data;

        if (this.el) {
            //如果确实存在这个元素 我们在进行编译
            //把所有元素插入文档碎片中
            this.fragment = this.nodeToFragment(this.el);
            //把数据编译到对应的元素上
            this.compiler(this.fragment);
            //把编译好的文档碎片插入到el上
            this.el.appendChild(this.fragment);
        }
    }
    //辅助方法
    isElement(node) {
        return node.nodeType === 1;
    }
    isDirective(name) {
        return name.includes('v-');
    }
    
    //核心方法
    nodeToFragment(node) {
        const fragment = document.createDocumentFragment();
        while(node.firstChild) {
            fragment.appendChild(node.firstChild);
        }
        return fragment;
    }
    compiler(fragment) {
        const childNodes = fragment.childNodes;
        childNodes.forEach(node => {
            if (this.isElement(node)) {
                //是元素节点
                this.compilerElement(node);
                this.compiler(node);
            }else {
                //是文本节点
                this.compilerText(node);
            }
        })
    }
    compilerElement(node) {
        const attrs = node.attributes;
        Array.from(attrs).forEach(attr => {
            if (this.isDirective(attr.name)) {
                const expr = attr.value;
                const directiveName = attr.name.split('-')[1];
                compilerUtil[directiveName](node, this.data, expr);
            }
        })
    }
    compilerText(node) {
        const text = node.textContent;
        const reg = /\{\{([^}]+)\}\}/g;
        if (reg.test(text)) {
            compilerUtil.text(node, this.data, text);
        }
    }
}

compilerUtil = {
    model(node, data, expr) {
        const value = this.getVal(data, expr);
        this.updater.modelUpdater(node, value);
        new Watcher(data, expr, () => {
            this.updater.modelUpdater(node, this.getVal(data, expr));
        })
        //双向绑定
        node.oninput = e => {
            const newValue = e.target.value;
            this.setValue(data, expr, newValue);
        }
    },
    text(node, data, text) {
        const value = this.getText(data, text)
        this.updater.textUpdater(node, value);
        text.replace(/\{\{([^}]+)\}\}/g, (...arg) => {
            new Watcher(data, arg[1].trim(), () => {
                this.updater.textUpdater(node, this.getText(data, text));
            })
        })
    },
    updater: {
        modelUpdater(node, value) {
            node.value = value;
        },
        textUpdater(node, value) {
            node.textContent = value;
        }
    },
    getVal(data, expr) {
        return expr.split('.').reduce((a, current) => a[current], data);
    },
    getText(data, text) {
        return text.replace(/\{\{([^}]+)\}\}/g, (...arg) => {
            return this.getVal(data, arg[1].trim())
        })
    },
    setValue(data, expr, value) {
        const keyArr = expr.split('.');
        keyArr.reduce((a, current, index) => {
            if (index === keyArr.length - 1) {
                a[current] = value;
            }
            return a[current];
        }, data)
    }
}

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

        if (this.$el && this.$data) {
            //如果用户传了el和data 我们才进行编译
            //数据劫持
            new Observer(this.$data);
            //数据编译到元素上
            new Compiler(this.$el, this.$data);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值