本教程说明将采用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://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。