建立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; } } }
后续内容更精彩