美文网首页
MVVM原理解析下

MVVM原理解析下

作者: 增商 | 来源:发表于2019-12-27 22:17 被阅读0次

    首先看效果截图


    图片.png
    // 观察者(发布订阅) 观察者 被观察者
    class Dep {
      constructor() {
        this.subs = [];
      }
      addSub(watcher) {
        this.subs.push(watcher);
      }
      notify() {
        this.subs.forEach(wather => wather.update());
      }
    }
    //new Watcher
    class Watcher {
      constructor(vm, expr, callb) {
        this.vm = vm;
        this.expr = expr;
        this.cb = callb;
        //默认先存放老值
        this.oldValue = this.get();
      }
      get() {
        Dep.target = this;
        let value = CompileUtil.getVal(this.vm, this.expr);
        Dep.target = null;
        return value;
      }
      update() {
        //更新操作,数据变化后会调用观察者的update方法
        let newVal = CompileUtil.getVal(this.vm, this.expr);
        if (newVal != this.oldValue) {
          this.cb(newVal);
        }
      }
    }
    
    //实现数据劫持功能=>解构赋值
    class Observer {
      constructor(data) {
        console.log(data);
        this.observer(data);
      }
      observer(data) {
        //依次遍历,先判断合法性,如果数据没写是空的就不用监控了
        //if object => observer 如果是对象就观察
        if (data && typeof data == "object") {
          //if object => for
          for (let key in data) {
            this.defineReactive(data, key, data[key]);
            //data数据定义key属性 值是data[key]
          }
        }
      }
      defineReactive(obj, key, value) {
        let dep = new Dep();
        this.observer(value);
        Object.defineProperty(obj, key, {
          get() {
            //创建watcher时会 取到对应的内容并且把watcher放到全局上
            Dep.target && dep.addSub(Dep.target);
            //默认get方法取它未来的value
            return value;
          },
          set: newVal => {
            //{school:{name:'imycode'}} school={}
            if (newVal != value) {
              this.observer(newVal);
              value = newVal;
              dep.notify();
              //if current time value==newVal 如果
              //当前的老值和新值一样就不要进到这个if里
              //赋值
            }
          }
        });
      }
    }
    
    //基础类
    class Compiler {
      constructor(el, vm) {
        // console.log(el);
        this.vm = vm;
        //判断el属性是不是一个元素,如果不是元素那就获取他
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        // console.log(this.el); //dom节点
        let fragment = this.node2fragment(this.el);
        // console.log(fragment);
        //再内存中处理好
        //节点的内容进行替换
    
        //编译模板数据驱动
        this.compile(fragment);
        //然后再渲染给页面
        this.el.appendChild(fragment);
      }
      //编译文本的-看看有没{}
      isDirective(attrName) {
        return attrName.startsWith("v-");
      }
      compileText(node) {
        //{{xxx}} {{aaa}}
        let content = node.textContent;
        // console.log(content, "内容");
        if (/\{\{(.+?)\}\}/.test(content)) {
          // console.log(content); //找到所有文本元素开始填充
          CompileUtil["text"](node, content, this.vm);
        }
      }
      //编译元素的-看看有没v-model
      compileElement(node) {
        //取dom元素的属性
        let attributes = node.attributes; //类数组
        // console.log(attributes);
        //判断这些属性里有没有v-model的如果有我就找到了
        [...attributes].forEach(attr => {
          //v-model="school.name" type="text"
          // console.log(attr);
          let { name, value: expr } = attr;
          //结构  - v-model="school.name"
          // console.log(name, value);
          //判断name是不是v-开头的,是不是指令
          if (this.isDirective(name)) {
            let [, directive] = name.split("-");
            // console.log(node, "element");
            //找到了
            CompileUtil[directive](node, expr, this.vm);
            //需要调用不同的指令来处理 - v-model="school.name"
          }
        });
      } //核心的编译方法
      compile(node) {
        //用来编译内存中的dom节点
        let childNodes = node.childNodes;
        // console.log(childNodes);
        [...childNodes].forEach(child => {
          //是不是元素,不是元素就是文本
          //元素有v-model 文本有{{...}}
          if (this.isElementNode(child)) {
            // console.log("element  ", child);
            this.compileElement(child);
            // 如果是元素的话再去遍历子节点
            this.compile(child);
          } else {
            // console.log("text  ", child);
            this.compileText(child);
          }
        });
      }
      //把节点移动到内存中
      node2fragment(node) {
        //把当前元素中的东西都拿到
        //一个一个拿childnode儿子节点,都拿到
        let fragment = document.createDocumentFragment();
        //创建一个文档碎片
        let firstChild;
        //node指最外层的div#app
        while ((firstChild = node.firstChild)) {
          //不停拿第一个塞到内存最后页面中的节点都没有了就为null循环自然结束
          //拿一个少一个不用担心死循环
          //appendChild具有移动性
          // console.log(firstChild);
    
          fragment.appendChild(firstChild);
        }
        return fragment;
      }
      isElementNode(node) {
        //是不是元素节点
        return node.nodeType === 1; //undefined===1? 不相等
      } //=>#app false
    }
    //编译工具
    CompileUtil = {
      getVal(vm, expr) {
        //vm.$data 'school.name' 根据表达式取到最终的数据
        return expr.split(".").reduce((data, current) => {
          return data[current];
        }, vm.$data);
      },
      model(node, expr, vm) {
        //给输入框赋予value属性 node.value=xx
        // vm[expr]=vm.$data['...']错误
        let fn = this.updater["modelUpdater"];
        new Watcher(vm, expr, newVal => {
          fn(node, newVal);
        });
        let value = this.getVal(vm, expr); //返给我imycode
        fn(node, value);
      }, //node节点expr表达式school.name, vm当前实例
      html() {
        //node.innerHTML=xx
      },
      //遍历表达式将内容重新替换成一个完整的内容返还回去
      getContentValue(vm, expr) {
        return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
          return this.getVal(vm, args[1]);
        });
      },
      text(node, expr, vm) {
        let fn = this.updater["textUpdater"];
        //expr=>{{a}} {{b}} {{c}} => a b c
        let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
          //给表达式每个人都加上观察者,只要数据变了我就更新, 每个大括号
          new Watcher(vm, args[1], () => {
            fn(node, this.getContentValue(vm, expr)); //返回了一个全的字符串
          });
          return this.getVal(vm, args[1]);
        });
        //最终文本内容
        fn(node, content);
      },
      updater: {
        //把数据插入到节点中
        modelUpdater(node, value) {
          node.value = value;
        },
        htmlUpdater() {},
        //处理文本节点
        textUpdater(node, value) {
          node.textContent = value;
        }
      }
    };
    class Vue {
      constructor(options) {
        //this.$el $data $options
        this.$el = options.el;
        // console.log("options.el:  " + options.el);
    
        this.$data = options.data;
        // console.log("options.data:  " + options.data);
        //把数据全部转化成Object.defineProperty来定义
        new Observer(this.$data);
        //这个根元素存在编译模板
        if (this.$el) {
          new Compiler(this.$el, this);
        }
      }
    }
    
    

    这里需要注意的是,现在只做到了数据驱动视图,视图无法更改model

    相关文章

      网友评论

          本文标题:MVVM原理解析下

          本文链接:https://www.haomeiwen.com/subject/jatjoctx.html