const textReg = /\{\{\s*\w+\s*\}\}/gi; // 检测{{name}}语法
若含有{{}}语法,我们则可以对其处理,由于一个文本节点可能出现多个{{}}语法,因此编译含有{{}}语法的文本节点主要有以下两步:
- 找出该文本节点中所有依赖的属性,并且保留原始文本信息,根据原始文本信息还有属性值,生成最终的文本信息。比如说,原始文本信息是"test {{test}} {{name}}",那么该文本信息依赖的属性有this.data.test和this.data.name,那么我们可以根据原本信息和属性值,生成最终的文本。
- 为该文本节点所有依赖的属性注册Watcher函数,当依赖的属性发生变化的时候,则更新文本节点的内容。
class MVVM {
constructor({ data, el }) {
this.data = data;
this.el = el;
this.init();
this.initDom();
}
initDom() {
const fragment = this.node2Fragment();
this.compile(fragment);
// 将fragment返回到页面中
document.body.appendChild(fragment);
}
compile(node) {
const textReg = /\{\{\s*\w+\s*\}\}/gi; // 检测{{name}}语法
if (this.isTextNode(node)) {
// 若是文本节点,则判断是否有{{}}语法,如果有的话,则编译{{}}语法
let textContent = node.textContent;
if (textReg.test(textContent)) {
// 对于 "test{{test}} {{name}}"这种文本,可能在一个文本节点会出现多个匹配符,因此得对他们统一进行处理
// 使用 textReg来对文本节点进行匹配,可以得到["{{test}}", "{{name}}"]两个匹配值
const matchs = textContent.match(textReg);
CompileUtils.compileTextNode(this.data, node, matchs);
}
}
// 若节点有子节点的话,则对子节点进行编译
if (node.childNodes && node.childNodes.length > 0) {
Array.prototype.forEach.call(node.childNodes, (child) => {
this.compile(child);
})
}
}
// 是否是文本节点
isTextNode(node) {
return node.nodeType === 3;
}
}
const CompileUtils = {
reg: /\{\{\s*(\w+)\s*\}\}/, // 匹配 {{ key }}中的key
// 编译文本节点,并注册Watcher函数,当文本节点依赖的属性发生变化的时候,更新文本节点
compileTextNode(vm, node, matchs) {
// 原始文本信息
const rawTextContent = node.textContent;
matchs.forEach((match) => {
const keys = match.match(this.reg)[1];
console.log(rawTextContent);
new Watcher(vm, keys, () => this.updateTextNode(vm, node, matchs, rawTextContent));
});
this.updateTextNode(vm, node, matchs, rawTextContent);
},
// 更新文本节点信息
updateTextNode(vm, node, matchs, rawTextContent) {
let newTextContent = rawTextContent;
matchs.forEach((match) => {
const keys = match.match(this.reg)[1];
const val = this.getModelValue(vm, keys);
newTextContent = newTextContent.replace(match, val);
})
node.textContent = newTextContent;
}
}