本教程说明将采用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; } }}数据双向绑定(完结篇)后续内容更精彩