vue 数据又向绑定原理

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>myVue</title>
</head>
<body>
    <div id="myVue">
        <h1>{{title}}</h1>
        <input v-model="name" />
        <p>{{name}}</p>
        <button v-on:click="vueFun">click me</button>
        <p>{{name}}</p>
        <p>{{name}}</p>
    </div>
<script>
    
//递归兼听每个数据属性
function defineReactive(data,key,val){
    observe(val);
    var sup = new Dep();
    Object.defineProperty(data,key,{
        enumerable: true,
        configurable: true,
        get:function(){
            if(Dep.target){//是否需要添加订阅者
                sup.addSub(Dep.target)
            }
            return val;
        },
        set:function(newVal){
            if(val===newVal){
                return ;
            }
            val = newVal;
            sup.notify()
        }
    });
}
//兼听器
function observe(data){
    if(!data || typeof data != "object"){
        return ;
    }
    Object.keys(data).forEach(function(key){
        defineReactive(data,key,data[key]);
    });
}
//订阅器
function Dep(){
    this.subs=[];
}
Dep.target=null;
Dep.prototype={
    addSub:function(sub){
        this.subs.push(sub);
    },
    notify:function(sub){
        console.log(this.subs.length)
        this.subs.forEach(function(sub){
            sub.update();
        })
    }
}
//订阅者
function Watcher(vm,exp,cb){
    this.vm=vm;
    this.exp=exp;
    this.cb=cb;
    this.value= this.get();
}
Watcher.prototype={
    update:function(){
        this.run();
    },
    run:function(){
        var value = this.vm.data[this.exp];
        var oldVal = this.value;
        if(value!==oldVal){
            this.value=value;
            this.cb.call(this.vm,value,oldVal);
        }
    },
    get:function(){
        Dep.target = this;
        var value = this.vm.data[this.exp];
        Dep.target=null;
        return value;
    }
}

function Compile(el,vm){
    this.vm=vm;
    this.el = document.querySelector(el);
    this.fragment = null;
    this.init();
}

Compile.prototype={
    init:function(){
        if(this.el){
            this.fragment = this.nodeToFragment(this.el);
            this.compileElement(this.fragment);
            this.el.appendChild(this.fragment);
        }else{
            console.log("Dom not find");
        }
    },
    nodeToFragment:function(el){
        var fragment = document.createDocumentFragment();
        var child = el.firstChild;
        while(child){
            fragment.appendChild(child);
            child = el.firstChild;
        }
        return fragment;
    },
    compileElement:function(el){
        var childNodes = el.childNodes;
        var self = this;
        if(!childNodes){
            console.log("childNodes not find");
            return false;
        }
        [].slice.call(childNodes).forEach(function(node){
            var reg = /\{\{(.*)\}\}/;
            var text = node.textContent;
            if(self.isElementNode(node)){
                self.compile(node);
            }else if(self.isTextNode(node)&&reg.test(text)){
                self.compileText(node,reg.exec(text)[1]);
            }
            
            if(node.childNodes&&node.childNodes.length){
                self.compileElement(node);
            }
        })
    },
    compileText:function(node,exp){
        var self = this;
        var initText = this.vm[exp];
        this.updateText(node,initText);
        new Watcher(this.vm,exp,function(value){
            self.updateText(node,value);
        })
    },
    updateText:function(node,value){
        node.textContent = typeof value =="undefined"?"":value;
    },
    isTextNode:function(node){
        return node.nodeType==3;
    },
    compile:function(node){
        var nodeAttrs = node.attributes;
        var self = this;
        Array.prototype.forEach.call(nodeAttrs,function(attr){
            var attrName = attr.name;
            if(self.isDirective(attrName)){
                var exp = attr.value;
                var dir = attrName.substring(2);
                if(self.isEventDirective(dir)){//指令事件
                    self.compileEvent(node,self.vm,exp,dir);
                }else{
                    self.compileModel(node,self.vm,exp,dir);
                }
                node.removeAttribute(attrName);
            }
        })
    },
    compileEvent:function(node,vm,exp,dir){
        var eventType = dir.split(":")[1];
        var cb = vm.methods && vm.methods[exp];
        if(eventType && cb){
            node.addEventListener(eventType,cb.bind(vm),false);
        }
    },
    compileModel:function(node,vm,exp,dir){
        var self = this;
        var val = this.vm[exp];
        this.modelUpdate(node,val);
        new Watcher(this.vm,exp,function(value){
            self.modelUpdate(node,value);
        });
        node.addEventListener("input",function(e){
            var newValue = e.target.value;
            if(val===newValue){
                return;
            }
            self.vm[exp]=newValue;
            val =  newValue;
        })
    },
    modelUpdate:function(node,value){
        node.value = typeof value =="undefined"?"":value;
    },
    isDirective:function(attr){
        return attr.indexOf("v-")===0;
    },
    isEventDirective:function(dir){
        return dir.indexOf("on:")===0;
    },
    isElementNode:function(node){
        return node.nodeType==1;
    }
}

//把 observe 和 watcher  连接起来
function myVue(options){
    var self = this;
    this.vm = this;
    this.data = options.data;
    this.methods = options.methods;
    Object.keys(this.data).forEach(function(key){
        self.proxyKeys(key);
    })
    observe(this.data);
    new Compile(options.el,this.vm);
    options.mounted.call(this);
    return this;
}
myVue.prototype={
    proxyKeys:function(key){
        var self = this;
        Object.defineProperty(this,key,{
            enumerable:false,
            configurable:true,
            get:function proxyGetter(){
                return self.data[key]
            },
            set:function proxySetter(newVal){
                self.data[key]=newVal;
            }
        });
    }
}

var mvue = new myVue({
    el:"#myVue",
    data:{
        title:"index",
        name:"wellcome index"
    },
    methods:{
        vueFun(){
            this.name="this is ok"
            console.log("success")
        }
    },
    mounted:function(){
        var self = this;
        window.setTimeout(function(){
            self.title="success";
        },2000);
    }
});


</script>

</body>
</html>

猜你喜欢

转载自my.oschina.net/u/3449950/blog/2221540
今日推荐