MVVM雙向綁定簡單實現

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <style>
    </style>
</head>
<body>
    <div id='app'>
        <input type="text" v-model="hello"/>
        {{hello}}
        <div>
            <div>{{name}}</div>
        </div>
    </div>
    <script>
        //發佈者
        function Dep(){
            this.subs=[]
        }
        Dep.prototype.addSub = function(_watcher){
            this.subs.push(_watcher);
        }
        Dep.prototype.upDate = function(){
            // console.log(this.subs)
            this.subs.map(_watcher=>{
                console.log(_watcher)
                _watcher.upDate();
            })
        }
        //訂閱者
        function watcher(vm,node,name){
            Dep.target = this;
            this.vm=vm;
            this.node=node;
            this.name=name;
            this.upDate();
            Dep.target=null;
        }
        watcher.prototype.upDate=function(){
            console.log('被調用啦???')
            this.node.nodeValue=this.vm[this.name];
        }

        function MVVM(opt){
            this.id = opt.el;
            this.data = opt.data;
            this.observe(this.data,this)
            this.dom = this.nodeToFragment(document.getElementById(this.id),this);
            document.getElementById(this.id).append(this.dom);
        }
        MVVM.prototype.nodeToFragment = function(dom,vm){
            //把#app下的全部保存到文檔碎片中
            var frag = document.createDocumentFragment();
            var child;
            while(child = dom.firstChild){
                var _child = this.complie(child,vm)
                frag.append(_child);
            }
            return frag;
        }

        MVVM.prototype.complie = function(dom,vm){
            // 這裏主要是把#app 下的dom節點全部解析 
            var reg = /\{\{(.*)\}\}/;
            switch(dom.nodeType){
                case 1://dom節點;
                    var atte = dom.attributes;
                    for(var i=0;i<atte.length;i++){
                        if(atte[i].nodeName == "v-model"){
                            var name = atte[i].nodeValue;
                            
                            dom.addEventListener('input',function(e){
                                vm[name]=e.target.value;
                            })
                            dom.value = vm[name];
                            dom.removeAttribute("v-model");
                        }
                    }
                    dom.append(this.nodeToFragment(dom,vm));
                    break;
                case 3://文本節點
                    var value = dom.nodeValue;
                    if(reg.test(value)){
                        var name = RegExp.$1;
                        name = name.trim();
                        new watcher(vm,dom,name);//對每個文本節點,當做是一個訂閱者
                    }
                    break;
            }
            return dom;
        }
        MVVM.prototype.observe = function(obj,vm){
            //將data下的數據全部進行get set監聽,然後放置到根對象下
            Object.keys(obj).map( key =>{
                this.defineProperty(vm,key,obj[key])
            })
        }
        MVVM.prototype.defineProperty=function(obj,key,val){
            var dep = new Dep();
            Object.defineProperty(obj,key,{
                get:function(){
                    //把該字段當做發佈者
                    //把調用該字段的dom節點當做訂閱者
                    if(Dep.target) dep.addSub(Dep.target);
                    console.log('需要取數據',key,'<>',val)
                    return val;
                },
                set:function(newVal){
                    if(val !== newVal){
                        console.log('需要更新數據',val,"--->",newVal)
                        val=newVal;
                        dep.upDate();
                        //發佈者進行全局更新
                    }
                }
            })
        }
        var vue = new MVVM({
            el:'app',
            data:{
                hello:'hello world',
                name:'小明'
            }
        })
    </script>
</body>
</html>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章