建立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) } } }
// 建立一個模板編譯工具 class TemplateCompiler { // el 視圖 // vm 全局vm對象 constructor(el, vm) { // 緩存重要屬性 this.el = document.querySelector(el); this.vm = vm; } }
當緩存好重要的屬性後,就要解析模板了es6
步驟分三步數組
把內存的結果,放回到模板緩存
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()方法下面咱們來實現性能
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) { } }
DocumentFragments 是DOM節點。它們不是主DOM樹的一部分。一般的用例是建立文檔片斷,將元素附加到文檔片斷,而後將文檔片斷附加到DOM樹。在DOM樹中,文檔片斷被其全部的子元素所代替。this
由於文檔片斷存在於內存中,並不在DOM樹中,因此將子元素插入到文檔片斷時不會引發頁面迴流(對元素位置和幾何上的計算)。所以,使用文檔片斷一般會帶來更好的性能。
首先獲取每個子節點,而後遍歷每個節點,再判斷節點類型
下面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 = { // 解析text指令 text(node, vm, expr) { // 1. 找到更新方法 let updaterFn = this.updater['textUpdater']; // 執行方法 updaterFn && updaterFn(node, vm.$data[expr]); }, // 更新規則對象 updater: { // 文本更新方法 textUpdater(node, value) { node.textContent = value; } } }
isDirective(attrName) { // 判斷屬性是不是指令 return attrName.indexOf('v-') >= 0; }
// 建立一個模板編譯工具 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; } } }
修改以下代碼
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(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; } } }
後續內容更精彩