如何實現一個MVVM

說一說我對於mvvm模型的理解吧

我第一次接觸mvvm也是在學習vue的時候,在我看來vue和react都是數據驅動視圖,可是vue屬於標準的mvvm模型,react是從組件化演變而來html

很少廢話,直接粘圖vue

第一次使用mvvm的時候感受特別的神奇,我只是修改了數據就能夠驅動視圖的改變

  • 學習mvvm模型的做用node

    一開始就是在學習vue的使用還有vuex等等不少,也能作一些小的網站,但就是沒有辦法提高本身的vue到一個更高的境界,後來就不斷的往深了學習react

    聽過網上的這麼一句話

    編程世界和武俠世界比較像,每個入門的程序員,都幻想本身有朝一日,神功大成,青衣長劍,救民於水火之中,可是其實大部分的人一開始學習方式就錯了,致使一直沒法進入高手的行列,就是過於看中招式,武器,而忽略了內功的修煉,因此任你慕容復有百家武學,還有被我喬峯一招制敵,因此這就是內功差距程序員

原理就是內功修煉的捷徑vuex

進入主題

  • 原理
    Object.defineProperty(obj,name,{get:function(),set:function()})編程

  • 手寫
    mvvm主要分爲兩部
  • kvue.js
  1. 獲取數據,先獲取options
  2. 把options.data的數據經過Object.key()解析
  3. 進入主題 Obejct.defineProprety() 進行雙向綁定
  4. 接下來是兩個類 Dep 和 Watcher (關係能夠看上面的圖片)
  • compile.js
  1. 獲取dom宿主節點 options.el
  2. 把宿主節點拿出來遍歷,高效 createDocumentFragment()
  3. 編譯過程 判斷是不是文本節點,若是是文本節點就經過正則的分組獲取到{{}}插值表達式中間的值
  4. 更新函數 初始化更新函數,調用Watcher

第一次寫,怕說不明白,直接粘上代碼app

代碼

  • kvue.js
// new KVue({data:{...}})

class KVue {
  constructor(options) {
    this.$options = options;

    // 數據響應化
    this.$data = options.data;
    this.observe(this.$data);

    // 模擬一下watcher建立
    // new Watcher();
    // // 經過訪問test屬性觸發get函數,添加依賴
    // this.$data.test;
    // new Watcher();
    // this.$data.foo.bar;

    new Compile(options.el, this);

    // created執行
    if (options.created) {
        options.created.call(this);
    }
  }

  observe(value) {
    if (!value || typeof value !== "object") {
      return;
    }

    // 遍歷該對象
    Object.keys(value).forEach(key => {
      this.defineReactive(value, key, value[key]);
    //   代理data中的屬性到vue實例上
      this.proxyData(key);
    });
  }

  // 數據響應化
  defineReactive(obj, key, val) {
    this.observe(val); // 遞歸解決數據嵌套

    const dep = new Dep();

    Object.defineProperty(obj, key, {
      get() {
        Dep.target && dep.addDep(Dep.target);
        return val;
      },
      set(newVal) {
        if (newVal === val) {
          return;
        }
        val = newVal;
        // console.log(`${key}屬性更新了:${val}`);
        dep.notify();
      }
    });
  }

  proxyData(key) {
      Object.defineProperty(this, key, {
          get(){
            return this.$data[key]
          },
          set(newVal){
            this.$data[key] = newVal;
          }
      })
  }

}

// Dep:用來管理Watcher
class Dep {
  constructor() {
    // 這裏存放若干依賴(watcher)
    this.deps = [];
  }

  addDep(dep) {
    this.deps.push(dep);
  }

  notify() {
    this.deps.forEach(dep => dep.update());
  }
}

// Watcher
class Watcher {
  constructor(vm, key, cb) {
      this.vm = vm;
      this.key = key;
      this.cb = cb;

    // 將當前watcher實例指定到Dep靜態屬性target
    Dep.target = this;
    this.vm[this.key]; // 觸發getter,添加依賴
    Dep.target = null;
  }

  update() {
    // console.log("屬性更新了");
    this.cb.call(this.vm, this.vm[this.key]);
  }
}
  • complie.js
// 用法 new Compile(el, vm)

class Compile {
  constructor(el, vm) {
    // 要遍歷的宿主節點
    this.$el = document.querySelector(el);

    this.$vm = vm;

    // 編譯
    if (this.$el) {
      // 轉換內部內容爲片斷Fragment
      this.$fragment = this.node2Fragment(this.$el);
      // 執行編譯
      this.compile(this.$fragment);
      // 將編譯完的html結果追加至$el
      this.$el.appendChild(this.$fragment);
    }
  }

  // 將宿主元素中代碼片斷拿出來遍歷,這樣作比較高效
  node2Fragment(el) {
    const frag = document.createDocumentFragment();
    // 將el中全部子元素搬家至frag中
    let child;
    while ((child = el.firstChild)) {
      frag.appendChild(child);
    }
    return frag;
  }
  // 編譯過程
  compile(el) {
    const childNodes = el.childNodes;
    Array.from(childNodes).forEach(node => {
      // 類型判斷
      if (this.isElement(node)) {
        // 元素
        // console.log('編譯元素'+node.nodeName);
        // 查找k-,@,:
        const nodeAttrs = node.attributes;
        Array.from(nodeAttrs).forEach(attr => {
          const attrName = attr.name; //屬性名
          const exp = attr.value; // 屬性值
          if (this.isDirective(attrName)) {
            // k-text
            const dir = attrName.substring(2);
            // 執行指令
            this[dir] && this[dir](node, this.$vm, exp);
          }
          if (this.isEvent(attrName)) {
            const dir = attrName.substring(1); // @click
            this.eventHandler(node, this.$vm, exp, dir);
          }
        });
      } else if (this.isInterpolation(node)) {
        // 文本
        // console.log('編譯文本'+node.textContent);
        this.compileText(node);
      }

      // 遞歸子節點
      if (node.childNodes && node.childNodes.length > 0) {
        this.compile(node);
      }
    });
  }

  compileText(node) {
    // console.log(RegExp.$1);
    this.update(node, this.$vm, RegExp.$1, "text");
  }

  // 更新函數
  update(node, vm, exp, dir) {
    const updaterFn = this[dir + "Updater"];
    // 初始化
    updaterFn && updaterFn(node, vm[exp]);
    // 依賴收集
    new Watcher(vm, exp, function(value) {
      updaterFn && updaterFn(node, value);
    });
  }

  text(node, vm, exp) {
    this.update(node, vm, exp, "text");
  }

  //   雙綁
  model(node, vm, exp) {
    // 指定input的value屬性
    this.update(node, vm, exp, "model");

    // 視圖對模型響應
    node.addEventListener("input", e => {
      vm[exp] = e.target.value;
    });
  }

  modelUpdater(node, value) {
    node.value = value;
  }

  html(node, vm, exp) {
    this.update(node, vm, exp, "html");
  }

  htmlUpdater(node, value) {
    node.innerHTML = value;
  }

  textUpdater(node, value) {
    node.textContent = value;
  }

  //   事件處理器
  eventHandler(node, vm, exp, dir) {
    //   @click="onClick"
    let fn = vm.$options.methods && vm.$options.methods[exp];
    if (dir && fn) {
      node.addEventListener(dir, fn.bind(vm));
    }
  }

  isDirective(attr) {
    return attr.indexOf("k-") == 0;
  }
  isEvent(attr) {
    return attr.indexOf("@") == 0;
  }
  isElement(node) {
    return node.nodeType === 1;
  }
  // 插值文本
  isInterpolation(node) {
    return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
  }
}
相關文章
相關標籤/搜索