vue源码深入解读MVVM(视图模板引擎),你真的了解双向绑定(v-model),数据劫持(observe),发布订阅模式吗?带你手鲁mvvm引擎。源码奉上(详细注释)!

11 篇文章 2 订阅

#1.vue的强大之处不必细说,vue的核心v-model的实现原理,网上都有很多。但是真正自己实现双向绑定,mvvm的源码却几乎没见过。

#1.2本人根据源码的解读,理解,以及借鉴网上的视频教程,手写一份mvvm,希望能帮助更多的vue学习者。

#2.先看成果

在这里插入图片描述

#3.实现原理

#3.1.数据劫持(observe)+模板编译(compile)+数据监听(watch)+发布订阅(Dep)

在这里插入图片描述

#4.源码

#4.1mvvm(架构)

class MVVM{
    constructor(optins){
        //1.先将传入的参数进行挂载
          this.$el=optins.el;
          this.$data=optins.data;

        //2.进行模板编译
        if(this.$el){
            //先进行数据劫持
            new Observe(this.$data);
            new Compile(this.$el,this);
        }
    }
}

#4.2模板编译(conpile)

class Compile{
    constructor(el,vm) {             //el挂载名称,vm 是 WrM实例
          this.el=this.isElememntNode(el)?el:document.querySelector(el);    //进行判断,挂载节点
          this.vm=vm;

          if(this.el){                //
                 //1.首先取出节点,获取到节点
                 let fragment=this.node2fragment(this.el)
                 //2.编译=>提取想要的元素节点 w-model和 文本节点 {{}}
                     this.compile(fragment);
                 //2.将节点放入到文档碎片中(内存)  fragment

                 //3.将编译好的fragment放回到真实dom中
                 this.el.appendChild(fragment);
          }
    }   

    /* 辅助方法*/
    //1.判断是不是节点
    isElememntNode(node){
         return  node.nodeType===1;
    }
    //判断元素节点是否包含w-指令
    isDirective(attrName){
        return attrName.includes('w-');
    }
    /*核心方法 */

    //1.将dom节点移入内存中
    node2fragment(el){     //将el中的内容全部放到内存中
          let fragment=document.createDocumentFragment();//开辟内存存储空间
          let firstChild;      //定义为第一个节点                        
          while(firstChild= el.firstChild){              //每次将取到的第一个节点放入内存
              fragment.appendChild(firstChild)
          }
          return fragment;
    }
    //2.进行编译
    compile(fragment){
        //递归
        let nodeChild=fragment.childNodes;
        Array.from(nodeChild).forEach(node=>{
              if(this.isElememntNode(node)){  //如果是元素,证明里面可能嵌套子节点,递归
                  //元素节点
                  //编译元素
                  this.compileElememnt(node);
                   
                  this.compile(node);
              }
              else{
                  //文本节点
                  //编译文本
                  this.compileText(node);
              }
        })
    }
    //2.1编译元素
    compileElememnt(node){
         //带有 v-model 等指令的
         let attrs=node.attributes;  //取出当前元素的属性
         Array.from(attrs).forEach(attr=>{
             //判断属性名字是否包含v-
             let attrName=attr.name;
             if(this.isDirective(attrName)){  
                    //取值,放到节点的值中
                    let expr=attr.value;
                    //
                    let type=attrName.slice(2);
                    // node this.vm.$data expr
                    compileUtile[type](node,this.vm,expr);
             }
         })
    }
    
    //2.2文本编译
    compileText(node){
           //有{{}}
           let expr = node.textContent; //取文本中的节点
           let reg= /\{\{([^}]+)\}\}/g  //正则表达式,匹配{{}}中的内容
           if(reg.test(expr)){
                // node this.vm.$data expr
                compileUtile['text'](node,this.vm,expr);
           }
        }
}
//编译工具 compileUtile 
compileUtile={
    //取值操作
    getVal(vm,expr){
       expr=expr.split('.');     //分割成数组
     return  expr.reduce((prev,next)=>{ //收敛
           return prev[next];
       },vm.$data)
    },
    getTextVal(vm,expr){
         return  expr=expr.replace(/\{\{([^}]+)\}\}/g,(...arguments)=>{   
            return this.getVal(vm,arguments[1]);
          })
    },
    text(node,vm,expr){  //文本处理
            let ex=expr; 
           let updatafn=this.updata['textUpdater'];
           let value=this.getTextVal(vm,expr);
           expr=expr.replace(/\{\{([^}]+)\}\}/g,(...arguments)=>{
            new watch(vm,arguments[1],()=>{
                updatafn && updatafn(node,this.getTextVal(vm,ex)); 
            });
      })
           updatafn && updatafn(node,value); 
    },
     
     set(vm,expr,value){
      expr= expr.split('.');
      return expr.reduce((prev,next,currnindex)=>{
          if(currnindex===expr.length-1){
              return prev[next]=value;
          }
          return prev[next];
      },vm.$data)
     },
    model(node,vm,expr){ //输入框处理
       let updatafn=this.updata['modelUpdater'];
       new watch(vm,expr,(newvalue)=>{
        updatafn && updatafn(node,this.getVal(vm,expr)); 
       })
       node.addEventListener('input',(e)=>{
           let newValue=e.target.value;
           this.set(vm,expr,newValue);
       })
       updatafn && updatafn(node,this.getVal(vm,expr)); 
    },

    updata:{
        //文本更新  {{}}
        textUpdater(node,value){
             node.textContent=value;
        },
        //输入框更新 w-model
        modelUpdater(node,value){
            node.value=value;
        }
    }
}

#4.3数据劫持(observe)+发布订阅(dep)

class Observe{
   constructor(data) {
       //开始数据劫持
    this.observe(data);   
   }
   observe(data){
       if(!data || typeof data !=='object')//判断是否为对象类型
       {
        return;
       }
       else{
          //开始劫持
          Object.keys(data).forEach(key=>{
              this.definReacative(data,key,data[key]);
              this.observe(data[key]);        //深度递归
          });
       }
   }
   //定义响应式
   definReacative(obj,key,value){
         let that=this;
         let dep=new Dep();
         Object.defineProperty( obj,key,{
              enumerable:true,      //是否可枚举
              configurable:true,    
              get(){
                  Dep.target && dep.addSub(Dep.target);
                    return value;
              },
              set(newValue){
              if(newValue!=value){
                
                that.observe(newValue);   
                value=newValue; 
                dep.notify();
                }
              }
             });
   }
}
            //发布订阅
class Dep{
    constructor(){
        this.subs=[]  //设置订阅数组
    }
    addSub(watch){
        this.subs.push(watch);   //添加到数组中  
    }
    notify(){
        this.subs.forEach(watch=>watch.updata()); //发布
    }
}

#4.4mvvm架构

class MVVM{
    constructor(optins){
        //1.先将传入的参数进行挂载
          this.$el=optins.el;
          this.$data=optins.data;

        //2.进行模板编译
        if(this.$el){
            //先进行数据劫持
            new Observe(this.$data);
            new Compile(this.$el,this);
        }
    }
}

#5.使用方法

引入上述4个文件
w-model (数据的双向绑定) {{}} (插值表达式)

#6.开发不易,点个赞吧。希望一键三连,

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南工gjl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值