手撕VUE源码(一):手写一个MVVM

class Vue{
  constructor(options){
    this.$el = options.el; //获取元素节点
    this.$data = options.data; //获取数据
    //若元素节点不为空
    if(this.$el){

      new Observer(this.$data); //数据劫持

      new Compiler(this); //解析节点变为虚拟dom,放到内存中
    }
  }
}

class Observer{
  constructor(data){
    this.observer (data);
  }

  observer(data){
    //判断是否为对象
    if(data && typeof data == 'object'){
      for(let key in data){
        this.reactive(key,data[key],data); //对key属性绑定数据劫持
      }
    }
  }

  reactive(key,value,data){
    
    this.observer(value); //对嵌套的子属性继续递归劫持
    
    Object.defineProperties(data,key,{
      get(){
        return value;
      },
      set: newValue => { //
        if(newValue != value){
          
          value = newValue;
          
          this.observer(newValue);//若新值为一个对象则对新对象再次劫持
        }
      }
    })
  }
}

class Compiler{
  constructor(vm){
    this.vm = vm;
    //判断el是元素节点还是元素ID
    this.el = this.isElementNode(vm.$el)?el:document.querySelector(el);
    
    let fregment = this.getChildNodes(this.el); //将子元素放入内存
    
    //将模板语法转换成绑定的数据
    this.compilerToData(fregment);
  }

  compilerToData(fregment){
    //开始转换
    [...fregment.childNodes].forEach(child =>{
      //判断子节点类型是元素还是文本节点
      if(isElementNode(child)){
        this.compilerElementNode(child); //转换元素节点
        this.compilerToData(child); //递归遍找到所有的孩子
      }else{
        this.compilerTextNode(child); //转换文本节点
      }
    })
  }

  compilerElementNode(node){
    let attributes = node.attributes; //节点的attributes属性也是个类数组
    [...attributes].forEach( attr => {
      let {name:vueDirect, value:express} = attr; //v-model="jsc" 对象结构,冒号后面代表将属性的值赋给新的变量名
      //判断属性是否为vue指令
      if(this.isVueDirect(vueDirect)){
        let [,vueDirect] = vueDirect.split("-") //对象结构,对“v-model”再做一次分割
        CompilerMapUtil[vueDirect](node,this.vm,express); //CompilerMapUtil是一个映射关系,不同的vue指令对应不同的解析处理方式
      }
    })
  }

  compilerTextNode(node){
    let textcontent = node.textcontent; //获取文本内容
    if(/\{\{(.+?)\}\}/.test(textcontent)) {
      CompilerMapUtil["text"](node,vm,textcontent)
    }
  }
  
  isVueDirect(name){
    if(name.startsWith("v-")){
      return true; 
    }
    return false;
  }

  getChildNodes(el){
    let fregment = document.createDocumentFragment();
    let firstChilNode;
    //每次取一个子节点放入内存中,直到取完为止
    while(firstChilNode = el.firstChild){
      fregment.appendChild(firstChilNode);
    }
    return fregment;
  }

  isElementNode(node){
    return node.nodeType ===1;
  }
}

//vue指令及解析处理关系映射
CompilerMapUtil = {

  model(node,vueObject,express){
    
    //根据model属性的变量名字express,从vue对象vm中取数据(jsc:11111,express的值是jsc)
    let modelValue = this.getValue(vueObject,express);
    
    let fn = this.updater['modelUpdater'] //获取model指令对应的赋值方法
    
    fn(node,modelValue); //将vue对象中的值赋给输入框
  },
  text(node,vueObject,content){
    //正则表达式的g表示全局,意为找出所有{{}}进行替换
    //因为箭头函数没有arguments,因此使用...args剩余参数表示法,可以将类数组arguments转换成数组类型变量args接收
    let textContent = content.replace(/\{\{(.+?)\}\}/g,(...args)=>{
      //replace的回调函数可以设置5个参数,该函数的第一个参数是匹配模式的子串。第二个参数是与模式中子表达式匹配的子串
      //args第一个参数是正则表达式匹配的{{jsc}},第二个参数是子表达式匹配到的jsc
      return this.getValue(vueObject,args[1]);
    })

    let fn = this.updater['textUpdater'] // 获取文本节点对应的赋值方法

    fn(node,textContent); //进行赋值操作
  },
  getValue(vueObject,express){ 
    //express在这里是jsc.name(v-model="jsc.name")
    //reduce会遍历[jsc,name]这个数组,并把vue的数据对象作为第一次的dataObject参数
    //每一次的返回值作为下一次的dataObject入参
    express.split(".").reduce( (dataObject,currentKey) => {
      return dataObject[currentKey];
    },vueObject.$data)
  },
  updater:{
    modelUpdater(node,modelValue){
      node.value = modelValue;
    },
    textUpdater(node, modelValue){
      node.textcontent = modelValue
    }
  },

  for(){

  },

  if(){

  }
}

猜你喜欢

转载自www.cnblogs.com/jiangxiaoxi/p/12590671.html
今日推荐