摘要:它們不是主樹的一部分。在樹中,文檔片段被其所有的子元素所代替。因為文檔片段存在于內存中,并不在樹中,所以將子元素插入到文檔片段時不會引起頁面回流對元素位置和幾何上的計算。因此,使用文檔片段通常會帶來更好的性能。
本教程說明將采用es6語法來編寫
創建MiniVue.js文件
//創建一個MVVM類 class MVVM { // 構造器 constructor(option) { // 緩存重要屬性 this.$vm = this; this.$el = option.el; this.$data = option.data; } }
MVVM類的作用: 解析視圖模板,把對應的數據,渲染到視圖
首先得判斷視圖是否存在,在視圖存在的時候,創建模板編譯器,來解析視圖
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; } }
當緩存好重要的屬性后,就要解析模板了
步驟分三步
把模板內容放進內存(內存片段)
解析模板
把內存的結果,放回到模板
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); }
}
上面定義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樹中,文檔片段被其所有的子元素所代替。
因為文檔片段存在于內存中,并不在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; } } }數據雙向綁定(完結篇)
后續內容更精彩
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/100953.html
摘要:項目地址和的區別其實和最大的區別就是多了一個虛擬,其他的區別都是很小的。 項目地址 Vue1和Vue2的區別 其實Vue1和Vue2最大的區別就是Vue2多了一個虛擬DOM,其他的區別都是很小的。所以理解了Vue1的源碼,就相當于理解了Vue2,中間差了一個虛擬DOM的Diff算法 文檔 數據雙向綁定 Vue主流程走向 組件 nextTick異步更新 MVVM 先來科普一下MVVM...
摘要:市面上竟然擁有多個虛擬庫。虛擬庫,就是出來后的一種新式庫,以虛擬與算法為核心,屏蔽操作,操作數據即操作視圖。及其他虛擬庫已經將虛擬的生成交由與處理了,因此不同點是,虛擬的結構與算法。因此虛擬庫是分為兩大派系算法派與擬態派。 去哪兒網迷你React是年初立項的新作品,在這前,去哪兒網已經深耕多年,擁有QRN(react-native的公司制定版),HY(基于React的hybird方案)...
摘要:,大家好,好久不賤呢最近因為看了一些的小說,整個人都比較致郁就在昨天,我用了一天的時間寫了,又一個小而美的前端框架可能你覺得,有了和,沒必要再寫一個了我覺得我還是想想辦法尋找一下它的存在感吧先看的組件化方案最先看到的應該是。 halo,大家好,好久不賤呢! 最近因為看了一些 be 的小說,整個人都比較致郁::>__+ {state.count--}}>- ...
閱讀 3571·2023-04-26 02:05
閱讀 2010·2021-11-19 11:30
閱讀 4218·2021-09-30 09:59
閱讀 3178·2021-09-10 10:51
閱讀 2610·2021-09-01 10:30
閱讀 1484·2021-08-11 11:20
閱讀 2619·2019-08-30 15:54
閱讀 568·2019-08-30 10:49