簡單實現迷你Vue框架

本教程說明將採用es6語法來編寫

建立MiniVue.js文件html

//建立一個MVVM類
class MVVM {
  // 構造器
  constructor(option) {
    // 緩存重要屬性
    this.$vm = this;
    this.$el = option.el;
    this.$data = option.data;
  }
}

MVVM類的做用: 解析視圖模板,把對應的數據,渲染到視圖vue

首先得判斷視圖是否存在,在視圖存在的時候,建立模板編譯器,來解析視圖node

class MVVM {
  // 構造器
  constructor(option) {
    // 緩存重要屬性
    this.$vm = this;
    this.$el = option.el;
    this.$data = option.data;
    // 判斷視圖是否存在
    if (this.$el) {
      // 建立模板編譯器, 來解析視圖
      this.$compiler = new TemplateCompiler(this.$el, this.$vm)
    }
  }
}

下面咱們來建立文件TemplateCompiler.js, 輸入如下內容

// 建立一個模板編譯工具
class TemplateCompiler {
  // el 視圖
  // vm 全局vm對象
  constructor(el, vm) {
    // 緩存重要屬性
    this.el = document.querySelector(el);
    this.vm = vm;
  }
}

當緩存好重要的屬性後,就要解析模板了es6

步驟分三步數組

  1. 把模板內容放進內存(內存片斷)
  2. 解析模板
  3. 把內存的結果,放回到模板緩存

    class TemplateCompiler {app

    // el 視圖
    // vm 全局vm對象
    constructor(el, vm) {
      // 緩存重要屬性
      this.el = document.querySelector(el);
      this.vm = vm;
      
      // 1. 把模板內容放進內存(內存片斷)
      let fragment = this.node2fragment(this.el);
      // 2. 解析模板
      this.compile(fragment);
      // 3. 把內存的結果,放回到模板
      this.el.appendChild(fragment);
    }

    }工具

上面定義node2fragment()方法和compile()方法下面咱們來實現性能

分析TemplateCompiler 類有兩大類方法:工具方法、核心方法

class TemplateCompiler {
      // el 視圖
      // vm 全局vm對象
      constructor(el, vm) {
        // 緩存重要屬性
        this.el = document.querySelector(el);
        this.vm = vm;
    
        // 1. 把模板內容放進內存(內存片斷)
        let fragment = this.node2fragment(this.el)
        // 2. 解析模板
        this.compile(fragment);
        // 3. 把內存的結果,放回到模板
        this.el.appendChild(fragment);
       }
      }

  // 工具方法
  isElementNode(node) {
    // 1. 元素節點  2. 屬性節點  3. 文本節點
    return node.nodeType === 1;
  }
  isTextNode(node) {
    return node.nodeType === 3;
  }

  // 核心方法
  node2fragment(node) {
    // 1. 建立內存片斷
    let fragment = document.createDocumentFragment();
    // 2. 把模板內容放進內存
    let child;
    while (child = node.firstChild) {
      fragment.appendChild(child);
    }
    // 3. 返回
    return fragment;
  }
  compile(node) {
  }
}

關於createDocumentFragment的描述:

DocumentFragments 是DOM節點。它們不是主DOM樹的一部分。一般的用例是建立文檔片斷,將元素附加到文檔片斷,而後將文檔片斷附加到DOM樹。在DOM樹中,文檔片斷被其全部的子元素所代替。this

由於文檔片斷存在於內存中,並不在DOM樹中,因此將子元素插入到文檔片斷時不會引發頁面迴流(對元素位置和幾何上的計算)。所以,使用文檔片斷一般會帶來更好的性能。

分析解析模板compile()方法實現方式

首先獲取每個子節點,而後遍歷每個節點,再判斷節點類型
下面childNode是類數組沒有數組方法

使用擴展運算符( spread )[...childNode]使其轉化爲數組便於操做

compile(parent) {
    // 1. 獲取子節點
    let childNode = parent.childNodes;
    // 2. 遍歷每個節點
    [...childNode].forEach(node => {
      // 3. 判斷節點類型
      if (this.isElementNode(node)) {
        // 解析元素節點的指令
        this.compileElement(node);
      }
    })
  }

下面來實現compileElement()方法 當前調用傳過來的是元素節點

接下來就要獲取元素的全部屬性,而後遍歷全部屬性,再判斷屬性是不是指令

v-text是vue的指令,像ng-xxx指令是不進行收集的

判斷爲指令時就要收集結果

compileElement(node) {
    // 1. 獲取當前節點的全部屬性
    let attrs = node.attributes;
    // 2. 遍歷當前元素的全部屬性
    [...attrs].forEach(attr => {
      let attrName = attr.name
      // 3. 判斷屬性是不是指令
      if (this.isDirective(attrName)) {
        // 4. 收集
        let type = attrName.substr(2); // v-text
        // 指令的值就是表達式
        let expr = attr.value;
        // 解析指令
        CompilerUtils.text(node, this.vm, expr);
      }
    })
  }

實現CompilerUtils類

CompilerUtils = {
  // 解析text指令
  text(node, vm, expr) {
    // 1. 找到更新方法
    let updaterFn = this.updater['textUpdater'];
    // 執行方法
    updaterFn && updaterFn(node, vm.$data[expr]);
  },
  // 更新規則對象
  updater: {
    // 文本更新方法
    textUpdater(node, value) {
      node.textContent = value;
    }
  }
}

在TemplateCompiler類工具方法下添加方法isDirective()判斷屬性是不是指令

isDirective(attrName) {
    // 判斷屬性是不是指令
    return attrName.indexOf('v-') >= 0;
  }

實現(v-text)完整代碼

// 建立一個模板編譯工具
class TemplateCompiler {
  // el 視圖
  // vm 全局vm對象
  constructor(el, vm) {
    // 緩存重要屬性
    this.el = document.querySelector(el);
    this.vm = vm;
    // 1. 把模板內容放進內存(內存片斷)
    let fragment = this.node2fragment(this.el)
    // 2. 解析模板
    this.compile(fragment);
    // 3. 把內存的結果,放回到模板
    this.el.appendChild(fragment);
  }
  // 工具方法
  isElementNode(node) {
    // 1. 元素節點  2. 屬性節點  3. 文本節點
    return node.nodeType === 1;
  }
  isTextNode(node) {
    return node.nodeType === 3;
  }
  isDirective(attrName) {
    // 判斷屬性是不是指令
    return attrName.indexOf('v-') >= 0;
  }
  // 核心方法
  node2fragment(node) {
    // 1. 建立內存片斷
    let fragment = document.createDocumentFragment();
    // 2. 把模板內容放進內存
    let child;
    while (child = node.firstChild) {
      fragment.appendChild(child);
    }
    // 3. 返回
    return fragment;
  }
  compile(parent) {
    // 1. 獲取子節點
    let childNode = parent.childNodes;
    // 2. 遍歷每個節點
    [...childNode].forEach(node => {
      // 3. 判斷節點類型
      if (this.isElementNode(node)) {
        // 元素節點 (解析指令)
        this.compileElement(node);
      }
    })
  }
  // 解析元素節點的指令
  compileElement(node) {
    // 1. 獲取當前節點的全部屬性
    let attrs = node.attributes;
    // 2. 遍歷當前元素的全部屬性
    [...attrs].forEach(attr => {
      let attrName = attr.name
      // 3. 判斷屬性是不是指令
      if (this.isDirective(attrName)) {
        // 4. 收集
        let type = attrName.substr(2); // v-text
        // 指令的值就是表達式
        let expr = attr.value;
        CompilerUtils.text(node, this.vm, expr);
      }
    })
  }
  // 解析表達式
  compileText() {
  }
}
CompilerUtils = {
  // 解析text指令
  text(node, vm, expr) {
    // 1. 找到更新方法
    let updaterFn = this.updater['textUpdater'];
    // 執行方法
    updaterFn && updaterFn(node, vm.$data[expr]);
  },
  // 更新規則對象
  updater: {
    // 文本更新方法
    textUpdater(node, value) {
      node.textContent = value;
    }
  }
}

下面來實現(v-model)

修改以下代碼

compileElement(node) {
  // 1. 獲取當前節點的全部屬性
  let attrs = node.attributes;
  // 2. 遍歷當前元素的全部屬性
  [...attrs].forEach(attr => {
    let attrName = attr.name
    // 3. 判斷屬性是不是指令
    if (this.isDirective(attrName)) {
      // 4. 收集
      let type = attrName.substr(2); // v-text
      // 指令的值就是表達式
      let expr = attr.value;
      //-----------------------修改代碼start---------------------
      // CompilerUtils.text(node, this.vm, expr);
      CompilerUtils[type](node, this.vm, expr);
      //-----------------------修改代碼end---------------------
    }
  })
}

在CompilerUtils類添加實現

CompilerUtils = {
  ...
  //----------------------新增方法---------------------
  // 解析model指令
  model(node, vm, expr) {
    // 1. 找到更新方法
    let updaterFn = this.updater['modelUpdater'];
    // 執行方法
    updaterFn && updaterFn(node, vm.$data[expr]);
  },
  // 更新規則對象
  updater: {
    ...
    //----------------------新增方法---------------------
    // 輸入框更新方法
    modelUpdater(node, value) {
      node.value = value
    }
  }
}

實現(v-html)就由讀者自行添加對應的方法,形式和(v-model)差很少

下面來實現解析表達式 ,在compile添加如下代碼

重要的怎麼用驗證表達式

compile(parent) {
    // 1. 獲取子節點
    let childNode = parent.childNodes;
    // 2. 遍歷每個節點
    [...childNode].forEach(node => {
      // 3. 判斷節點類型
      if (this.isElementNode(node)) {
        // 元素節點 (解析指令)
        this.compileElement(node);
      //-----------------新增代碼--------------------
        // 文本節點
      } else if (this.isTextNode(node)) {
        // 表達式解析
        // 定義表達式正則驗證規則
        let textReg = /\{\{(.+)\}\}/;
        let expr = node.textContent;
        // 按照規則驗證內容
        if (textReg.test(expr)) {
          // 獲取分組內容
          expr = RegExp.$1;
          // 調用方法編譯
          this.compileText(node, expr);
        }
      }
    })
  }

下面來實現文本解析器,經過分析(v-text)和表達式解析差很少

// 解析表達式
  compileText(node, expr) {
    CompilerUtils.text(node, this.vm, expr);
  }

完整實現代碼

// 建立一個模板編譯工具
class TemplateCompiler {
  // el 視圖
  // vm 全局vm對象
  constructor(el, vm) {
    // 緩存重要屬性
    this.el = document.querySelector(el);
    this.vm = vm;
    // 1. 把模板內容放進內存(內存片斷)
    let fragment = this.node2fragment(this.el)
    // 2. 解析模板
    this.compile(fragment);
    // 3. 把內存的結果,放回到模板
    this.el.appendChild(fragment);
  }
  // 工具方法
  isElementNode(node) {
    // 1. 元素節點  2. 屬性節點  3. 文本節點
    return node.nodeType === 1;
  }
  isTextNode(node) {
    return node.nodeType === 3;
  }
  isDirective(attrName) {
    // 判斷屬性是不是指令
    return attrName.indexOf('v-') >= 0;
  }
  // 核心方法
  node2fragment(node) {
    // 1. 建立內存片斷
    let fragment = document.createDocumentFragment();
    // 2. 把模板內容放進內存
    let child;
    while (child = node.firstChild) {
      fragment.appendChild(child);
    }
    // 3. 返回
    return fragment;
  }
  compile(parent) {
    // 1. 獲取子節點
    let childNode = parent.childNodes;
    // 2. 遍歷每個節點
    [...childNode].forEach(node => {
      // 3. 判斷節點類型
      if (this.isElementNode(node)) {
        // 元素節點 (解析指令)
        this.compileElement(node);
      } else if (this.isTextNode(node)) {
        // 表達式解析
        // 定義表達式正則驗證規則
        let textReg = /\{\{(.+)\}\}/;
        let expr = node.textContent;
        // 按照規則驗證內容
        if (textReg.test(expr)) {
          expr = RegExp.$1;
          // 調用方法編譯
          this.compileText(node, expr);
        }
      }
    })
  }
  // 解析元素節點的指令
  compileElement(node) {
    // 1. 獲取當前節點的全部屬性
    let attrs = node.attributes;
    // 2. 遍歷當前元素的全部屬性
    [...attrs].forEach(attr => {
      let attrName = attr.name;
      // 3. 判斷屬性是不是指令
      if (this.isDirective(attrName)) {
        // 4. 收集
        let type = attrName.substr(2); // v-text
        // 指令的值就是表達式
        let expr = attr.value;
        // CompilerUtils.text(node, this.vm, expr);
        CompilerUtils[type](node, this.vm, expr);
      }
    })
  }
  // 解析表達式
  compileText(node, expr) {
    CompilerUtils.text(node, this.vm, expr);
  }
}
CompilerUtils = {
  // 解析text指令
  text(node, vm, expr) {
    // 1. 找到更新方法
    let updaterFn = this.updater['textUpdater'];
    // 執行方法
    updaterFn && updaterFn(node, vm.$data[expr]);
  },
  // 解析model指令
  model(node, vm, expr) {
    // 1. 找到更新方法
    let updaterFn = this.updater['modelUpdater'];
    // 執行方法
    updaterFn && updaterFn(node, vm.$data[expr]);
  },
  // 更新規則對象
  updater: {
    // 文本更新方法
    textUpdater(node, value) {
      node.textContent = value;
    },
    // 輸入框更新方法
    modelUpdater(node, value) {
      node.value = value;
    }
  }
}

數據雙向綁定(完結篇)

後續內容更精彩
圖片描述

相關文章
相關標籤/搜索