原生js实现双向数据绑定mvvm

1,000 阅读1分钟

仓库链接:mvvm实现源码

<!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">
        <input type="text" value="" v-model="{{user.name}}"/>
        {{user.name}}
    </div>
    <script src="vm.js"></script>
    <script>
        let vm = new Vue({
            el:'#app'
            ,data:{
                user:{
                    name:'杨文宇'
                }
            }
        })
    </script>
</body>
</html>
class Vue{
    constructor(options){
        this.$el = Vue.getEl(options.el);
        this.$data = options.data;
        new Observe(this,this.$data);
        new Compile(this,this.$el);
    }

    static getEl(selector){
        return selector.nodeType == Node.ELEMENT_NODE ? selector : document.querySelector(selector)
    }
}

//劫持类
class Observe{
    constructor(vm,data){
        this.vm = vm;
        this.data = data;
        this.observer(data);
        //创建代理
        this.dataProxy(vm,data);
    }
    //对数据进行劫持
    observer(data){
        Object.keys(data).forEach(key=>{
            this.reactive(data,key,data[key])
        })
    }
    //响应式函数
    reactive(obj,key,value){
        typeof value == 'object' && this.observer(value)
        let _self = this
        //为每一个属性创建依赖容器,因为有可能一个属性会被多个地方依赖
        , dep = new Dep();
        Object.defineProperty(obj,key,{
            get(){
                //添加订阅者
                Dep.target && dep.subs.indexOf(Dep.target)<=-1 && dep.addSub(Dep.target)
                return value;
            }
            ,set(newVal){
                if(value == newVal){return}
                typeof newVal == 'object' && _self.observer(newVal);
                value = newVal;
                dep.notify();
            }
        })
    }

    dataProxy(obj,data){
        Object.keys(data).forEach(key=>{
            this.reactive(obj,key,data[key])
        })
    }
}

class Compile{
    constructor(vm,el){
        this.el = el;
        this.vm = vm;
        this.ready();
    }

    static isDirect(attrName){
        return /^v-/.test(attrName)
    }

    static isTextNode(node){
        return node.nodeType == Node.TEXT_NODE && /^\s*[^\s]+\s*$/.test(node.textContent)
    }

    static getVal(data,expOrFn){
        expOrFn = /^\{\{(.+?)\}\}/.exec(expOrFn)[1];
        return expOrFn.split(".").reduce((data,cur)=>{
            return data[cur];
        },data);
    }

    static setVal(data,expOrFn,value){
        expOrFn = /^\{\{(.+?)\}\}/.exec(expOrFn)[1];
        expOrFn.split(".").reduce((data,cur,index,arr)=>{
            if(index == arr.length-1){
                return data[cur] = value;
            }
            return data[cur];
        },data);
    }

    //指令集
    static directSet = {
        model(vm,node,expOrFn){
            new Watcher(vm,expOrFn,function(newVal){
                node.value = newVal;
                Dep.target = null;
            })
            node.value = Compile.getVal(vm.$data,expOrFn);
            node.addEventListener('input',e=>{
                Compile.setVal(vm.$data,expOrFn,e.target.value)
            })
        }
        ,text(vm,node,expOrFn){
            new Watcher(vm,expOrFn,function(newVal){
                node.textContent = newVal;
                Dep.target = null;
            })
            node.textContent = Compile.getVal(vm.$data,expOrFn);
        }
    }

    ready(){
        let fragment = this.node2fragment(this.el)
        this.compiler(fragment);
        this.el.appendChild(fragment);
    }

    //编译
    compiler(mountEl){
        let childNodes = [...mountEl.childNodes];
        childNodes.forEach(node=>{
            //获取不是文本节点或不是空文本的节点
            if(node.nodeType == Node.ELEMENT_NODE){
                [...node.attributes].forEach(attr=>{
                    let {name,value:expOrFn} = attr;
                    if(!Compile.isDirect(name)){return}
                    Compile.directSet[name.split('-')[1]](this.vm,node,expOrFn)
                })
            }
            if(Compile.isTextNode(node)){
                let expOrFn = node.textContent.trim();
                Compile.directSet['text'](this.vm,node,expOrFn)
            }
            node.childNodes.length > 0 && this.compiler(node)
        })
    }

    //将节点放到文档碎片流中
    node2fragment(el){
        let fragment = new DocumentFragment()
        , child;
        while(child = el.firstChild){
            fragment.appendChild(child)
        }
        return fragment;
    }
}


/**
 * 依赖收集--->收集的就是订阅对象
 */
class Dep{
    
    static target = null;

    constructor(){
        this.subs = [];
    }
    //添加订阅者
    //谁用到这个数据,谁就是订阅者,反映在html中就是使用数据的这个dom元素
    addSub(watcher){
        this.subs.push(watcher)
    }
    //通知所有订阅者
    notify(){
        this.subs.forEach(sub=>{
            sub.update();
        })
    }
}

/**
 * 谁用到这个数据,谁依赖这个数据,谁就是订阅者
 * 订阅者-->更新视图
 */
class Watcher{
    constructor(vm,expOrFn,cb){
        Dep.target = this;
        this.vm = vm;
        this.expOrFn = expOrFn;
        this.cb = cb;
        this.oldVal = Compile.getVal(vm.$data,expOrFn)
    }
    //更新视图
    update(){
        let value = Compile.getVal(this.vm.$data,this.expOrFn);
        if(this.oldVal != value){
            this.cb(value);
        }
    }
}