Vue双向绑定,观察者模式代码解析,修改版

215 阅读1分钟

#html部分

<div id="app">
{{name}}<input id="inputText"  type="text" v-model="name">{{age}}<input id="inputText"  type="text" v-model="age">
</div>

#js部分

var n = 0;
function Vue(opt){
    this.data = opt.data;
    let _id = document.querySelector(opt.el);
    //observe需要在compile之前这样,不然初始化赋值取不到值
    observe(this.data,this);
    let _dom = cloneNode(_id,this);
    _id.appendChild(_dom);
   
}
function observe(obj,vm){
    Object.keys(obj).forEach(key => {
        defineReactive(vm,key,obj[key])
    })
}
// 观察者
function Watcher(vm,node,name,nodeType){
    console.log('新')
    this.vm = vm;
    this.node = node;
    this.name = name;
    this.nodeType = nodeType;
    Dep.target = this;
    console.log(Dep.target);    
}
Watcher.prototype = {
    get:function(){
        this.value = this.vm[this.name]
    },
    updata:function(){
        this.get()
        if(this.nodeType == 'input'){
            this.node.value = this.value


        }
        if(this.nodeType == 'text'){
            this.node.nodeValue = this.value
        }

    }
}
//订阅者构造函数
function Dep(){
  this.subs = [] //存放我们观察者的一个数组

}
Dep.prototype = {
    notify:function(){
        console.log(this.subs);
        this.subs.forEach(sub =>{
            sub.updata()
        })

    },
    addSub:function(sub){
        console.log('没执行');
        this.subs.push(sub);
        //这里需要对sub,即watcher去重,可能实现的不是特别好
        this.subs = [...new Set(this.subs)]
        console.log(this.subs);
    }
}
function defineReactive(obj,key,val){
    //订阅者
    console.log(++n);
    var x = n;
    let dep = new Dep();
    Object.defineProperty(obj,key,{
        get:function(){
            //Dep.target 观察者实例 挂载到Dep
            if(Dep.target){
                console.log("Dep.target")
                console.log(Dep.target);
               dep.addSub(Dep.target)
            }
           return val
        },
        set:function(newValue){
          
           if(val == newValue){
              return
           }
           val = newValue
           console.log(`设置${newValue}`)
           dep.notify();
           
        }
    })
}
function compile(node,vm){
  
    console.log('compile');
    console.dir(node.nodeType)
    var name =''
    let reg = /\{\{(.*)\}\}/;
    let nodeType = 'input'
    if(node.nodeType == 1){
        let attr = node.attributes
        for(let i = 0,al=attr.length;i<al;i++){
            if(attr[i].nodeName == 'v-model'){
                nodeType = 'input'
                name = attr[i].nodeValue;
                /*new Watcher放在获取值getter之前,new Watcher 会直接Dep.target赋值,
                后面getter执行时,addSub进dep.subs(Dep实例),watcher不会少*/
                new Watcher(vm,node,name,nodeType)
                node.addEventListener('input',function(){
                    vm[name] = this.value;
                },false)
                node.value = vm[name]
                node.removeAttribute('v-model')        
            }
        }

    }
    if(node.nodeType == 3){
       // console.log(node.childNodes[0].nodeValue)
        if(reg.test(node.nodeValue)){
            nodeType = 'text'
            name = RegExp.$1;
            new Watcher(vm,node,name,nodeType)
            node.textContent = vm[name];
        }
    }
}

function cloneNode(node,vm){
let flag = document.createDocumentFragment()
let child
while(child = node.firstChild) {
 compile(child,vm)
 flag.appendChild(child)
}
return flag
}
var vm = new Vue({
    data:{name:'aaa',age:'30'},
    el:'#app'
})
//总结:
1.new Vue,首先进行el挂载,data绑定到observer上,node节点拦截,重新生成节点,
每个符合{{}}和v-model的节点,都会生成一个watcher,watcher构造函数里面,将this赋值给Dep.target
即是当前的Watcher实例,并且如果节点是input监听input输入,下一步,执行获取属性,Observer里面
的getter,这时Dep.target是之前刚赋值的Watcher实例,把它添加到这个属性的dep的subs数组中,
然后取值。
2.Observer是数据的监听,每个属性都有一个dep收集器,dep收集器里的subs存放着该属性的watcher,
当数据更新时,会触发Observer的setter会调用dep.notifys,执行该dep的subs的每个sub的updata方法,
根据不同的node结构,进行赋值