摘要:它們不是主樹(shù)的一部分。在樹(shù)中,文檔片段被其所有的子元素所代替。因?yàn)槲臋n片段存在于內(nèi)存中,并不在樹(shù)中,所以將子元素插入到文檔片段時(shí)不會(huì)引起頁(yè)面回流對(duì)元素位置和幾何上的計(jì)算。因此,使用文檔片段通常會(huì)帶來(lái)更好的性能。
本教程說(shuō)明將采用es6語(yǔ)法來(lái)編寫(xiě)
創(chuàng)建MiniVue.js文件
//創(chuàng)建一個(gè)MVVM類(lèi) class MVVM { // 構(gòu)造器 constructor(option) { // 緩存重要屬性 this.$vm = this; this.$el = option.el; this.$data = option.data; } }
MVVM類(lèi)的作用: 解析視圖模板,把對(duì)應(yīng)的數(shù)據(jù),渲染到視圖
首先得判斷視圖是否存在,在視圖存在的時(shí)候,創(chuàng)建模板編譯器,來(lái)解析視圖
class MVVM { // 構(gòu)造器 constructor(option) { // 緩存重要屬性 this.$vm = this; this.$el = option.el; this.$data = option.data; // 判斷視圖是否存在 if (this.$el) { // 創(chuàng)建模板編譯器, 來(lái)解析視圖 this.$compiler = new TemplateCompiler(this.$el, this.$vm) } } }下面我們來(lái)創(chuàng)建文件TemplateCompiler.js, 輸入以下內(nèi)容
// 創(chuàng)建一個(gè)模板編譯工具 class TemplateCompiler { // el 視圖 // vm 全局vm對(duì)象 constructor(el, vm) { // 緩存重要屬性 this.el = document.querySelector(el); this.vm = vm; } }
當(dāng)緩存好重要的屬性后,就要解析模板了
步驟分三步
把模板內(nèi)容放進(jìn)內(nèi)存(內(nèi)存片段)
解析模板
把內(nèi)存的結(jié)果,放回到模板
class TemplateCompiler {
// el 視圖 // vm 全局vm對(duì)象 constructor(el, vm) { // 緩存重要屬性 this.el = document.querySelector(el); this.vm = vm; // 1. 把模板內(nèi)容放進(jìn)內(nèi)存(內(nèi)存片段) let fragment = this.node2fragment(this.el); // 2. 解析模板 this.compile(fragment); // 3. 把內(nèi)存的結(jié)果,放回到模板 this.el.appendChild(fragment); }
}
上面定義node2fragment()方法和compile()方法下面我們來(lái)實(shí)現(xiàn)
分析TemplateCompiler 類(lèi)有兩大類(lèi)方法:工具方法、核心方法class TemplateCompiler { // el 視圖 // vm 全局vm對(duì)象 constructor(el, vm) { // 緩存重要屬性 this.el = document.querySelector(el); this.vm = vm; // 1. 把模板內(nèi)容放進(jìn)內(nèi)存(內(nèi)存片段) let fragment = this.node2fragment(this.el) // 2. 解析模板 this.compile(fragment); // 3. 把內(nèi)存的結(jié)果,放回到模板 this.el.appendChild(fragment); } } // 工具方法 isElementNode(node) { // 1. 元素節(jié)點(diǎn) 2. 屬性節(jié)點(diǎn) 3. 文本節(jié)點(diǎn) return node.nodeType === 1; } isTextNode(node) { return node.nodeType === 3; } // 核心方法 node2fragment(node) { // 1. 創(chuàng)建內(nèi)存片段 let fragment = document.createDocumentFragment(); // 2. 把模板內(nèi)容放進(jìn)內(nèi)存 let child; while (child = node.firstChild) { fragment.appendChild(child); } // 3. 返回 return fragment; } compile(node) { } }關(guān)于createDocumentFragment的描述:
DocumentFragments 是DOM節(jié)點(diǎn)。它們不是主DOM樹(shù)的一部分。通常的用例是創(chuàng)建文檔片段,將元素附加到文檔片段,然后將文檔片段附加到DOM樹(shù)。在DOM樹(shù)中,文檔片段被其所有的子元素所代替。
因?yàn)槲臋n片段存在于內(nèi)存中,并不在DOM樹(shù)中,所以將子元素插入到文檔片段時(shí)不會(huì)引起頁(yè)面回流(對(duì)元素位置和幾何上的計(jì)算)。因此,使用文檔片段通常會(huì)帶來(lái)更好的性能。
分析解析模板compile()方法實(shí)現(xiàn)方式首先獲取每一個(gè)子節(jié)點(diǎn),然后遍歷每一個(gè)節(jié)點(diǎn),再判斷節(jié)點(diǎn)類(lèi)型
下面childNode是類(lèi)數(shù)組沒(méi)有數(shù)組方法
使用擴(kuò)展運(yùn)算符( spread )[...childNode]使其轉(zhuǎn)化為數(shù)組便于操作
compile(parent) { // 1. 獲取子節(jié)點(diǎn) let childNode = parent.childNodes; // 2. 遍歷每一個(gè)節(jié)點(diǎn) [...childNode].forEach(node => { // 3. 判斷節(jié)點(diǎn)類(lèi)型 if (this.isElementNode(node)) { // 解析元素節(jié)點(diǎn)的指令 this.compileElement(node); } }) }
下面來(lái)實(shí)現(xiàn)compileElement()方法 當(dāng)前調(diào)用傳過(guò)來(lái)的是元素節(jié)點(diǎn)
接下來(lái)就要獲取元素的所有屬性,然后遍歷所有屬性,再判斷屬性是否是指令
v-text是vue的指令,像ng-xxx指令是不進(jìn)行收集的判斷為指令時(shí)就要收集結(jié)果
compileElement(node) { // 1. 獲取當(dāng)前節(jié)點(diǎn)的所有屬性 let attrs = node.attributes; // 2. 遍歷當(dāng)前元素的所有屬性 [...attrs].forEach(attr => { let attrName = attr.name // 3. 判斷屬性是否是指令 if (this.isDirective(attrName)) { // 4. 收集 let type = attrName.substr(2); // v-text // 指令的值就是表達(dá)式 let expr = attr.value; // 解析指令 CompilerUtils.text(node, this.vm, expr); } }) }實(shí)現(xiàn)CompilerUtils類(lèi)
CompilerUtils = { // 解析text指令 text(node, vm, expr) { // 1. 找到更新方法 let updaterFn = this.updater["textUpdater"]; // 執(zhí)行方法 updaterFn && updaterFn(node, vm.$data[expr]); }, // 更新規(guī)則對(duì)象 updater: { // 文本更新方法 textUpdater(node, value) { node.textContent = value; } } }在TemplateCompiler類(lèi)工具方法下添加方法isDirective()判斷屬性是否是指令
isDirective(attrName) { // 判斷屬性是否是指令 return attrName.indexOf("v-") >= 0; }實(shí)現(xiàn)(v-text)完整代碼
// 創(chuàng)建一個(gè)模板編譯工具 class TemplateCompiler { // el 視圖 // vm 全局vm對(duì)象 constructor(el, vm) { // 緩存重要屬性 this.el = document.querySelector(el); this.vm = vm; // 1. 把模板內(nèi)容放進(jìn)內(nèi)存(內(nèi)存片段) let fragment = this.node2fragment(this.el) // 2. 解析模板 this.compile(fragment); // 3. 把內(nèi)存的結(jié)果,放回到模板 this.el.appendChild(fragment); } // 工具方法 isElementNode(node) { // 1. 元素節(jié)點(diǎn) 2. 屬性節(jié)點(diǎn) 3. 文本節(jié)點(diǎn) return node.nodeType === 1; } isTextNode(node) { return node.nodeType === 3; } isDirective(attrName) { // 判斷屬性是否是指令 return attrName.indexOf("v-") >= 0; } // 核心方法 node2fragment(node) { // 1. 創(chuàng)建內(nèi)存片段 let fragment = document.createDocumentFragment(); // 2. 把模板內(nèi)容放進(jìn)內(nèi)存 let child; while (child = node.firstChild) { fragment.appendChild(child); } // 3. 返回 return fragment; } compile(parent) { // 1. 獲取子節(jié)點(diǎn) let childNode = parent.childNodes; // 2. 遍歷每一個(gè)節(jié)點(diǎn) [...childNode].forEach(node => { // 3. 判斷節(jié)點(diǎn)類(lèi)型 if (this.isElementNode(node)) { // 元素節(jié)點(diǎn) (解析指令) this.compileElement(node); } }) } // 解析元素節(jié)點(diǎn)的指令 compileElement(node) { // 1. 獲取當(dāng)前節(jié)點(diǎn)的所有屬性 let attrs = node.attributes; // 2. 遍歷當(dāng)前元素的所有屬性 [...attrs].forEach(attr => { let attrName = attr.name // 3. 判斷屬性是否是指令 if (this.isDirective(attrName)) { // 4. 收集 let type = attrName.substr(2); // v-text // 指令的值就是表達(dá)式 let expr = attr.value; CompilerUtils.text(node, this.vm, expr); } }) } // 解析表達(dá)式 compileText() { } } CompilerUtils = { // 解析text指令 text(node, vm, expr) { // 1. 找到更新方法 let updaterFn = this.updater["textUpdater"]; // 執(zhí)行方法 updaterFn && updaterFn(node, vm.$data[expr]); }, // 更新規(guī)則對(duì)象 updater: { // 文本更新方法 textUpdater(node, value) { node.textContent = value; } } }下面來(lái)實(shí)現(xiàn)(v-model)
修改如下代碼
compileElement(node) { // 1. 獲取當(dāng)前節(jié)點(diǎn)的所有屬性 let attrs = node.attributes; // 2. 遍歷當(dāng)前元素的所有屬性 [...attrs].forEach(attr => { let attrName = attr.name // 3. 判斷屬性是否是指令 if (this.isDirective(attrName)) { // 4. 收集 let type = attrName.substr(2); // v-text // 指令的值就是表達(dá)式 let expr = attr.value; //-----------------------修改代碼start--------------------- // CompilerUtils.text(node, this.vm, expr); CompilerUtils[type](node, this.vm, expr); //-----------------------修改代碼end--------------------- } }) }
在CompilerUtils類(lèi)添加實(shí)現(xiàn)
CompilerUtils = { ... //----------------------新增方法--------------------- // 解析model指令 model(node, vm, expr) { // 1. 找到更新方法 let updaterFn = this.updater["modelUpdater"]; // 執(zhí)行方法 updaterFn && updaterFn(node, vm.$data[expr]); }, // 更新規(guī)則對(duì)象 updater: { ... //----------------------新增方法--------------------- // 輸入框更新方法 modelUpdater(node, value) { node.value = value } } }
實(shí)現(xiàn)(v-html)就由讀者自行添加對(duì)應(yīng)的方法,形式和(v-model)差不多
下面來(lái)實(shí)現(xiàn)解析表達(dá)式 ,在compile添加以下代碼重要的怎么用驗(yàn)證表達(dá)式
compile(parent) { // 1. 獲取子節(jié)點(diǎn) let childNode = parent.childNodes; // 2. 遍歷每一個(gè)節(jié)點(diǎn) [...childNode].forEach(node => { // 3. 判斷節(jié)點(diǎn)類(lèi)型 if (this.isElementNode(node)) { // 元素節(jié)點(diǎn) (解析指令) this.compileElement(node); //-----------------新增代碼-------------------- // 文本節(jié)點(diǎn) } else if (this.isTextNode(node)) { // 表達(dá)式解析 // 定義表達(dá)式正則驗(yàn)證規(guī)則 let textReg = /{{(.+)}}/; let expr = node.textContent; // 按照規(guī)則驗(yàn)證內(nèi)容 if (textReg.test(expr)) { // 獲取分組內(nèi)容 expr = RegExp.$1; // 調(diào)用方法編譯 this.compileText(node, expr); } } }) }
下面來(lái)實(shí)現(xiàn)文本解析器,通過(guò)分析(v-text)和表達(dá)式解析差不多
// 解析表達(dá)式 compileText(node, expr) { CompilerUtils.text(node, this.vm, expr); }
完整實(shí)現(xiàn)代碼
// 創(chuàng)建一個(gè)模板編譯工具 class TemplateCompiler { // el 視圖 // vm 全局vm對(duì)象 constructor(el, vm) { // 緩存重要屬性 this.el = document.querySelector(el); this.vm = vm; // 1. 把模板內(nèi)容放進(jìn)內(nèi)存(內(nèi)存片段) let fragment = this.node2fragment(this.el) // 2. 解析模板 this.compile(fragment); // 3. 把內(nèi)存的結(jié)果,放回到模板 this.el.appendChild(fragment); } // 工具方法 isElementNode(node) { // 1. 元素節(jié)點(diǎn) 2. 屬性節(jié)點(diǎn) 3. 文本節(jié)點(diǎn) return node.nodeType === 1; } isTextNode(node) { return node.nodeType === 3; } isDirective(attrName) { // 判斷屬性是否是指令 return attrName.indexOf("v-") >= 0; } // 核心方法 node2fragment(node) { // 1. 創(chuàng)建內(nèi)存片段 let fragment = document.createDocumentFragment(); // 2. 把模板內(nèi)容放進(jìn)內(nèi)存 let child; while (child = node.firstChild) { fragment.appendChild(child); } // 3. 返回 return fragment; } compile(parent) { // 1. 獲取子節(jié)點(diǎn) let childNode = parent.childNodes; // 2. 遍歷每一個(gè)節(jié)點(diǎn) [...childNode].forEach(node => { // 3. 判斷節(jié)點(diǎn)類(lèi)型 if (this.isElementNode(node)) { // 元素節(jié)點(diǎn) (解析指令) this.compileElement(node); } else if (this.isTextNode(node)) { // 表達(dá)式解析 // 定義表達(dá)式正則驗(yàn)證規(guī)則 let textReg = /{{(.+)}}/; let expr = node.textContent; // 按照規(guī)則驗(yàn)證內(nèi)容 if (textReg.test(expr)) { expr = RegExp.$1; // 調(diào)用方法編譯 this.compileText(node, expr); } } }) } // 解析元素節(jié)點(diǎn)的指令 compileElement(node) { // 1. 獲取當(dāng)前節(jié)點(diǎn)的所有屬性 let attrs = node.attributes; // 2. 遍歷當(dāng)前元素的所有屬性 [...attrs].forEach(attr => { let attrName = attr.name; // 3. 判斷屬性是否是指令 if (this.isDirective(attrName)) { // 4. 收集 let type = attrName.substr(2); // v-text // 指令的值就是表達(dá)式 let expr = attr.value; // CompilerUtils.text(node, this.vm, expr); CompilerUtils[type](node, this.vm, expr); } }) } // 解析表達(dá)式 compileText(node, expr) { CompilerUtils.text(node, this.vm, expr); } } CompilerUtils = { // 解析text指令 text(node, vm, expr) { // 1. 找到更新方法 let updaterFn = this.updater["textUpdater"]; // 執(zhí)行方法 updaterFn && updaterFn(node, vm.$data[expr]); }, // 解析model指令 model(node, vm, expr) { // 1. 找到更新方法 let updaterFn = this.updater["modelUpdater"]; // 執(zhí)行方法 updaterFn && updaterFn(node, vm.$data[expr]); }, // 更新規(guī)則對(duì)象 updater: { // 文本更新方法 textUpdater(node, value) { node.textContent = value; }, // 輸入框更新方法 modelUpdater(node, value) { node.value = value; } } }數(shù)據(jù)雙向綁定(完結(jié)篇)
后續(xù)內(nèi)容更精彩
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/100953.html
摘要:項(xiàng)目地址和的區(qū)別其實(shí)和最大的區(qū)別就是多了一個(gè)虛擬,其他的區(qū)別都是很小的。 項(xiàng)目地址 Vue1和Vue2的區(qū)別 其實(shí)Vue1和Vue2最大的區(qū)別就是Vue2多了一個(gè)虛擬DOM,其他的區(qū)別都是很小的。所以理解了Vue1的源碼,就相當(dāng)于理解了Vue2,中間差了一個(gè)虛擬DOM的Diff算法 文檔 數(shù)據(jù)雙向綁定 Vue主流程走向 組件 nextTick異步更新 MVVM 先來(lái)科普一下MVVM...
摘要:市面上竟然擁有多個(gè)虛擬庫(kù)。虛擬庫(kù),就是出來(lái)后的一種新式庫(kù),以虛擬與算法為核心,屏蔽操作,操作數(shù)據(jù)即操作視圖。及其他虛擬庫(kù)已經(jīng)將虛擬的生成交由與處理了,因此不同點(diǎn)是,虛擬的結(jié)構(gòu)與算法。因此虛擬庫(kù)是分為兩大派系算法派與擬態(tài)派。 去哪兒網(wǎng)迷你React是年初立項(xiàng)的新作品,在這前,去哪兒網(wǎng)已經(jīng)深耕多年,擁有QRN(react-native的公司制定版),HY(基于React的hybird方案)...
摘要:最終選擇了兼容到的,終于使用上框架,雖然它只是個(gè)。沒(méi)有對(duì)比就沒(méi)有傷害本來(lái)想著技術(shù)棧統(tǒng)一,移動(dòng)端也準(zhǔn)備使用。于是,之后對(duì)移動(dòng)端的技術(shù)選型上更加慎重了,最終采用了文檔更漂亮的。易用還真不易用,坑還真多。 吐槽 avalon.js 歷史背景 需求重大調(diào)整,所有業(yè)務(wù)推倒重來(lái)(pc端主要任務(wù)涉及管理后臺(tái)類(lèi)型的網(wǎng)站); 開(kāi)發(fā)周期很緊,過(guò)年前要上線(xiàn); 公司pc端業(yè)務(wù)要求兼容到ie8; 2015年前...
摘要:,大家好,好久不賤呢最近因?yàn)榭戳艘恍┑男≌f(shuō),整個(gè)人都比較致郁就在昨天,我用了一天的時(shí)間寫(xiě)了,又一個(gè)小而美的前端框架可能你覺(jué)得,有了和,沒(méi)必要再寫(xiě)一個(gè)了我覺(jué)得我還是想想辦法尋找一下它的存在感吧先看的組件化方案最先看到的應(yīng)該是。 halo,大家好,好久不賤呢! 最近因?yàn)榭戳艘恍?be 的小說(shuō),整個(gè)人都比較致郁::>__+ {state.count--}}>- ...
閱讀 3577·2023-04-26 02:05
閱讀 2021·2021-11-19 11:30
閱讀 4230·2021-09-30 09:59
閱讀 3184·2021-09-10 10:51
閱讀 2614·2021-09-01 10:30
閱讀 1496·2021-08-11 11:20
閱讀 2626·2019-08-30 15:54
閱讀 572·2019-08-30 10:49