vue的MVVM响应式原理

vue的MVVM响应式原理

1、数据劫持

vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter.getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

2、完整代码

const compileUtil = {
     getVal(expr,vm){
       return expr.split('.').reduce((data,currentVal) => {
        console.log(currentVal);
        return data[currentVal];
       },vm.$data)
     },
     setVal(expr,vm,inputVal){
       return expr.split('.').reduce((data,currentVal) => {
        data[currentVal] = inputVal;
       },vm.$data);
     },
     getContentVal(expr, vm){
       retrun  expr.repalce(/\{\{(.+?)\}\}/g, (...args) => {
           return this.getVal(ages[1],vm);
          })
     },
     //处理text模式
     text(node,expr,vm) {//expr相当于msg msg在data里面的定义假定为:学习MVVM原理
         //const value = vm.$data[expr];
         //this.updater.textUpdater(node,value)
         //以上的写法并不是很严谨,只是可以将<div v-text='msg'></div>这样的进行正确的解析,那<div v-text='person.fav'></div>这样的应该如何处理呢?
         let value;
         if (expr.indexOf('{{')!== -1) {
          value = expr.repalce(/\{\{(.+?)\}\}/g, (...args) => {
          //在这里可以打印一下args看看拿到的到底是什么?
          //绑定观察者,将来数据发生变化  触发这里的回调 进行更新
          new Watcher(vm, args[1], (newVal) => {
           this.updatertextUpdater(node, this.getContentVal(expr,vm));
          })
           return this.getVal(ages[1],vm);
          })
         }else {
           value = this.getVal(expr,vm);
         }
         this.updater.textUpdater(node,value)
     },
     //处理html模式
     html(node.expr,vm) {
         const value = this.getVal(expr,vm);
         new Watcher(v, expr, (newVal) => {
          this.updater.htmlUpdater(node, newVal);
         })
         this.updater.htmlUpdater(node,value);
     },
     //处理model模式
     model(node.expr,vm) {
         const value = this.getVal(expr,vm);
         //绑定更新数据 数据驱动=》视图
         new Watcher(v, expr, (newVal) => {
          this.updater.modelUpdater(node, newVal);
         })
         //当视图进行变化的时候,数据也进行相应的变化 视图=》数据=》视图
         node.addEventListener('input', (e) => {
          this.setVal(expr, vm, e.target.value);
         })
         this.updater.modelUpdater(node,value);
     },
     on(node,expr,vm,eventName) {
          let fn = vm.$options.methods && vm.$options.methods[expr];
          node.addEventListener(eventName,fn.bind(vm),false);
     },
 //更新的对象
 updater:{
 	 modelUpdater(node,value){
     	node.value = value;
     },
     textUpdater(node, value){
        node.textContent = value;
     },
     htmlUpdater(node, value){
        node.innerHTML = value;
     }
 }
}
class Compile {
     constructor(el,vm) {
          this.el = this.isElementNode(el) ? el : document.querySelector(el);
          this.vm = vm;
          //1、获取文档碎片对象,放入内存中会减少页面的回流和重绘
          const fragment = this.node2Fragment(this.el);
          console.log(fragment);
          //2、编译模板
          this.compile(fragment);
          //3、追加子元素到根元素
          this.el.appendChild(fragment);
     }
 
 
     complie(fragment) {
          //1、获取子节点
          const childNodes = fragment.childNodes;
          [...childNodes].forEach(child => {
            if(this.isElementNode(child){
             //是元素节点,则编译元素节点
             this.compileElement(child);
            }else {
             //文本节点,编译文本节点
             this.compileText(child);
            }
            if(child.childNodes && child.childNodes.length) {
             this.compile(child);
            }
            )
          })
     }
 
 
     //编译元素
     compileElement(node){
          const attributes = node.attributes;
          [...attributes].forEach( attr => {
           const {name, value} = attr;
           //在这里可以打印看一下拿到的值
           console.log(name);
           //当是一个指令的时候
           if(this.isDirective(name)){//是一个指令:v-text v-html v-model v-on:click
              const [,dirctive] = name.split('-');//text,html,model,on:click
              const [dirName,eventName] = dirctive.split(':');//text html model on
              //更新数据  数据驱动视图
              compileUtil[dirName](node,value,this.vm,eventName)//value则是指令所对应的值比如:msg,this.vm是实例对象,eventName则是方法的名字
              //删除有指令标签上的属性
              node.removeAttribute('v-' + dirctive);
           }else if(this.isEventName(name)){ 
            //@click = 'handleClick'
            let [,eventName] = name.split('@');
             compileUtil['on'](node,value,this.vm,eventName)
           }
          })
     },
 
 
     //编译文本
     compileText(node){
     	console.log(node.textContent);//这里输出的是:全部的文本内容,里面会包含空以及其它,接下来需要做匹配,匹配到具有双大括号的
     	const content = node.textContent;
     	if(/\{\{(.+?)\}\}/.test(content)){
     	 //处理文本
     	 compileUtil['text'](node,content,this.vm);
     	}
     }
 
 
     //判断是不是指令
     isDirective(attrName) {
      	return attrName.startsWith('v-');
     }
     
     //判断是不是以@开头的
     isEventName(attrName) {
        return attrName.startsWith('@');
     }
 
 
     //创建文档碎片
     node2Fragment(el) {
          const f = document.createDocumentFragment();
          let firstChild;
          //在这里需要注意的while循环中的el.firstChild有移动的作用,也就是说:当将第一个值进行赋值之后,则第二个将变为el中的第一个,依次类推
          while(firstChild = el.firstChild){
           f.appendChild(firstChild)
          }
          return f;
     }
 
 
     //判断是否是元素节点的对象
     isElementNode(node) {
      	return node.nodeType === 1;
     }
    }


    class MVue {
         constructor(options) {
          this.$el = options.el;
          this.$data = options.data;
          this.$options = options;
          if(this.$el) {
           //1、实现一个数据观察者
           new Observer(this.$data);
           //2、实现一个指令解析器
           new Compile(this.$el,this);
           this.proxyData(this.$data);
          }
         }
         proxyData(data){
          for(const key in data){
          Object.defindeProperty(this,key , {
           get(){
            return data[key]
           },
           set(newVal){
            data[key] = newVal;
           }
          })
          }
         }
    }

3、数据的观察Observer

class Watcher {
 constructor(vm,expr,cb) {
  this.vm = vm;
  this.expr = expr;
  this.cb = cb;
  this.oldVal = this.getOldVal();
 }
 getOldVal() {
 Dep.target = this;
  compileUtil.getVal(this.expr,this.vm);
  Dep.target = null;
  return oldVal;
 }
 update() {
  const newVal = compileUtil.getVal(this.expr,this.vm);
  if(newVal !== this.oldVal){
  this.cb(newVal);
 }
 }
}

class Dep {
  constructor() {
   this.subs = [];
  }
  //收集观察者
  addSub(watcher) {
   this.subs.push(watcher);
  }
  //通知观察者去更新
  notify(){
   console.log('通知了观察者');
   this.subs.forEach(w => w.update())
  }
}

class Observer {
     constructor(data) {
		this.observe(data);
     }
     observe(data){
     	/*
     	{
     	  person: {
     	   name:'张三',
     	   fav: {
     	    a:'爱好'
     	   }
     	  }
     	}*/
     	if(data && typeof data === 'object') {
     	   Object.keys(data).forEach(key => {
     	     this.defineReactive(data,key, data[key]);
     	   })
     	}
     }
     defineReactive(obj,key,value){
      	//递归遍历
      	this.observe(value);
      	const dep = new Dep();
      	Object.defineProperty(obj,key,{
             enumerable:true,
             configurable;false,
      	 get(){
      	 	//订阅数据变化时,往Dep中添加观察者
      	 	Dep.target && dep.addSub(Dep.target);
      	  	return value;
      	 },
      	 set:(newValue) => {
             this.observe(newVal);
             if(newValue !== value) {
              value = newvalue
             }
             //告诉Dep通知变化
             dep.notify();
      	 }
      	})
     }
}

4、简述一下你所理解的MVVM响应式原理

vue采用数据劫持配合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变化时,发布消息给依赖收集器,做出对应的回调函数,去更新视图

MVVM作为绑定的入口,整合Observe,Compile和Watcher三者,通过Observe来监听数据的变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer,Compile之间的桥梁,达到数据变化=》更新视图,视图交互变化=》数据变化的双向绑定效果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值