MVVM原理之Vue中的运用

MVVM模式是Model-View-ViewModel的简写,即模型-视图-视图模型。【模型】指的是数据层。【视图】指的是视图层所看到的页面。【视图模型】mvvm模式的核心,它是连接view和model的桥梁。

数据层和视图层直接是不能直接通信的,那么模型(数据层)如何转换成视图:通过绑定数据。视图如何转换成数据:通过DOM事件监听。当这个两个都实现了,就完成了数据的双向绑定。MVVM模式Model和View是通过ViewModel作为桥梁进行通信的。

Vue实现数据响应式变化其原理就是MVVM原理,通过定义一个Watcher类(观察者),Dep类(被观察者)作为Model(数据层)和View(视图层)之间的桥梁。Vue通过模板编译(Compiler类)来实现数据层向视图层的转换,通过数据劫持(Observe类)来实现对数据的监听。通过在模板编译的过程中new 一个watcher对象,监控数据是否发生变化,如果数据发生变化则继续更新视图。通过数据劫持的过程中,通过收集每个唯一的watcher对象,在用户重新设置数据的时候,将唯一的watcher对象一一触发更新。则代码的实现过程如下:

1、实现一个模板编译Compiler类,主要的功能是生成一个虚拟DOM(提高其性能),通过更新虚拟DOM来间接更新真实DOM以及具体如何编译模板,将节点中的内容通过真实的数据进行替换。

class Compiler{
  constructor(el,vm){
    this.vm=vm;
    this.el=this.isElementNode(el)?el:document.querySelector(el);
    //把当前节点中的元素获取到放到内存中

    let fragment=this.node2fragment(this.el)
    //把节点中的内容进行替换

    //编译模板  用数据编译
    this.compile(fragment)
    //把这个内容在塞到页面中
    this.$el.appendChild(fragment);
  }
  isDirective(attrName){
    return attrName.startWith('v-');
  }
  compileElement(node){
    let attr=node.attributes;//类数组
    [...attr].forEach(atr=>{
      let {name,value:expr}=atr;
      if(this.isDirective(name)){
        console.log(node) //指令元素
        let[,directive]= name.split('-');
        let [directiveName,eventName]=directive.split('.');   //处理v-on:click指令
        CompilerUtils[directiveName](node,expr,)
        CompilerUtils[directive](node,expr,this.vm,eventName)
      }
    })
  }
  compileText(node){
    let content=node.textcontent;   //节点的文本
    if(/\{\{(.+?)\}\}/.test(content)){
      console.log(content)//找到所有文本
      CompilerUtils['text'](node,content,this.vm)
    }
  }
  //用来编译内存中的dom节点
  compile(node){
    let childNodes=node.childNodes;  //dom的第一层
    [...childNodes].forEach(child=>{
      if(this.isElementNode(child)){
        console.log('element');
        this.compileElement(child);
        //如果是元素的话,需要遍历子节点
        this.compile(child);
      }else{
        console.log('text');
        this.compileText(child)
      }
    })
  }
  node2fragment(node){
    //把所有的节点都拿到,创建一个文档碎片。
    let fragment=document.createDocumentFragment();
    let firstChild;
    while(firstChild=node.firstChild){
      fragment.appendChild(firstChild);
    }
    return fragment;  
  }
  isElementNode(node){
    return node.nodeType===1
  }
}
//处理不同指令不同的功能调用不同的处理方式
CompilerUtils={
  //获取值
  getValue(vm,expr){
    let value=expr.split('.').reduce((data,current)=>{
      return data[current];
    },vm.$data)
    return value;
  },
  //设置值
  setValue(vm,expr,value){
    return expr.split('.').reduce((data,current,index,arr)=>{
      if(index===arr.length-1){
        return data[current]=value;
      }
      return data[current];
    },vm.$data)
  },
  model(node,expr,vm){
    let fn=this.updater['modelUpdater']
    let value=this.getValue(vm,expr);
    new Watcher(vm,expr,(newValue)=>{
      fn(node,newValue);
    })
    node.addEventListener('input',(e)=>{
      let value=e.target.value;
      this.setValue(vm,expr,value);
    })
    fn(node,value);
  },
  on(node,expr,vm,eventName){
    node.addEventListener(eventName,(e)=>{
      return vm[expr].call(vm,e);
    })
  },
  html(){

  },
  getContentValue(vm,expr){
    return expr.replace(/\{\{(.+?)\}\}/g,(...agrs)=>{
      return this.getValue(vm,args[1]);
    })
  },
  text(node,expr,vm){ //expr {{a}} {{b}} {{c}}
    let content=expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
      new Watcher(vm,args[1],()=>{
        let value=this.getContentValue(vm,expr);    //返回了一个全的字符串
        fn(node,value)
      })
      return this.getValue(vm,args[1]);
    })
    let fn=this.updater['textUpdater'];
    fn(node,content);
  },
  updater:{
    modelUpdater(node,value){
      node.value=value;
    },
    htmlUpdater(node,value){
      node.innerHTML=value;
    },
    textUpdater(node,value){
      node.textContent=value; //将值放在节点的文本内容上
    }
  }
}

2、实现一个Observe类,将对象通过Object.defineProperty(obj,key,value)重新定义来劫持数据做一些其他的事情,数组通过重写数组的方法。

//vue内部是如何实现响应式数据变化的,响应式数据变化有什么特点
//实现响应式数据变化对象是通过其核心defineProperty()实现,数组是通过数组的方法进行重写。
// 特点: 使用对象的时候 必须先声明属性 ,这个属性才是响应式的
// 1.增加不存在的属性 不能更新视图 (vm.$set)
// 2。默认会递归增加 getter和setter
// 3.数组里套对象 对象是支持响应式变化的,如果是常量则没有效果
// 4.修改数组索引和长度 是不会导致视图更新的
// 5.如果新增的数据 vue中也会帮你监控(对象类型)
let arrayProto=Array.prototype;     //先存一份原生的原型
let proto=Object.create(Array.prototype);   //复制一份一模一样原生的原型。
['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(item=>{
    proto[item]=function(...ary){
        let insert;     //数组新增的元素也要进行观察
        switch(item){
            case 'push':insert=ary;
            case 'unshift':insert=ary;
            case 'splice':insert=ary.slice(2);
            default:
                break;
        }
        observer(insert);
        console.log('视图更新')
        return arrayProto[item].call(arrayProto,...ary);
    }
})

function observerArray(array){
    for(let i=0,len=array.length;i<len;i++){
        let item=array[i];
        observer(item);
    }
}
function observer(obj){
    if(typeof obj!=='object' || obj===null){
        return obj;
    }
    if(Array.isArray(obj)){
        Object.setPrototypeOf(obj,proto)
        observerArray(obj)
    }else{
        for(let key in obj){
            defineReactive(key,obj[key],obj)
        }
    }
}
function defineReactive(key,value,obj){
    observer(value);        //通过递归的方式观察对象的值是否也是对象,如果是对象,更改数据会发生视图层的变化
   Object.defineProperty(obj,key,{
       get:function(){
            return value;
       },
       set:function(newValue){
            if(newValue!==value){
                value=newValue;
                observer(newValue); //新值也要观测
                console.log('视图发生更新')
            }
       }
   })
}

3、通过观察者和被观察者模式实现Dep类(实现对watcher对象的订阅和发布watcher对象的更新方法)和Watcher类(实现对新老值的比较触发值变化之后的回调函数)将Compiler类和Observe类进行连接,给Observe添加被观察者对象,Compiler类添加观察着对象。当编译模板时,new Watcher生成一个对象的同时触发Watcher类中获取老数据的get函数,当获取数据时则又触发了通过Object.defineProperty()的get()方法,在这个方法当中new Dep 生成一个dep对象,通过dep对象收集watcher,最后在设置数据set()方法中发布watcher。

class Dep{
  constructor(){
    this.subs=[];   //存放watcher

  }
  //订阅
  addSubs(watcher){
    this.subs.push(watcher)
  }
  //发布
  notify(){
    this.subs.forEach(watcher=>{
      watcher.updater();
    })
  }
}
class Watcher{
  constructor(vm,expr,cb){
    this.vm=vm;
    this.expr=expr;
    this.cb=cb;
    this.oldval=this.get()
  }
  get(){
    Dep.target=this;
    let value=CompilerUtils.getValue(this.vm,this.expr);
    Dep.target=null;
    return value;
  }
  updater(){    //更新操作  数据变化后 会调用观察者的update方法
    let newValue=CompilerUtils.getValue(this.vm,this.expr);
    if(newValue!==this.oldval){
      this.cb(newValue);
    }
  }
}

猜你喜欢

转载自blog.csdn.net/Miss_hhl/article/details/105182732
今日推荐