{{}}语法只可能会出现在文本节点中,因此,我们只需要对文本节点做处理。如果文本节点中出现{{key}}这种语句的话,我们则对该节点进行编译。在这里,我们可以通过下面这个正则表达式来对文本节点进行处理,判断其是否含有{{}}语法。
const textReg = /\{\{\s*\w+\s*\}\}/gi; // 检测{{name}}语法 console.log(textReg.test('sss')); console.log(textReg.test('aaa{{ name }}')); console.log(textReg.test('aaa{{ name }} {{ text }}'));
若含有{{}}语法,我们则可以对其处理,由于一个文本节点可能出现多个{{}}语法,因此编译含有{{}}语法的文本节点主要有以下两步:
找出该文本节点中所有依赖的属性,并且保留原始文本信息,根据原始文本信息还有属性值,生成最终的文本信息。比如说,原始文本信息是"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; } }
结语
这样,一个具有v-model和{{}}功能的MVVM类就已经完成了
这里也有一个简单的样例(忽略样式)。
接下来的话,可能会继续实现computed属性,v-bind方法,以及支持在{{}}里面放表达式。如果觉得这个文章对你有帮助的话,麻烦点个赞,嘻嘻。
最后,贴上所有的代码: