一点点学会编译文本
使用 vue,都知道{{msg}}
,能变成data.msg
相对应的值hello
。
此文循序渐进实现最终vue中编译文本的效果。
{{msg}} => 'hello'
这里先不关注 dom 之类的事情,只专注于其中的逻辑。
首先看看以下代码,想想compileText
怎么实现
let data = { msg: "hello" }; let text = "{{msg}} world"; function compileText(data, text) { // 想想怎么实现 } text = compileText(data, text); // 想输出 => hello world console.log(text);
其实这里的逻辑并不难,无外乎先找到{{msg}}
,然后将整那个字符串替换成data.msg
的值。
function compileText(data, text) { // 匹配正则 let reg = /\{\{(.+)\}\}/; let newText = text.replace(reg, (...args) => { // 拿到msg let expr = args[1]; // data里面msg属性的值 let value = data[expr]; return value; }); // 返回替换完的新字符串 return newText; }
但是 data 里可能也有对象,如{msg:'hello',person:{name:'hua'}}
text 可能就变成{{person.name}} 喜欢西湖
{{person.name}} => 'hua'
其实就是在 compileText
里拿到 value
值的时候,多些操作。
思考下面的 getValue
怎么实现
let data = { person: { name: "hua" } }; let expr = "person.name"; function getValue(data, expr) { // 想想这里怎么写 } // 想输出 => hua console.log(getValue(data, expr));
这里其实就是一层层往下返回,为了写成公用的,会考虑很多层的情况。 理解难点是reduce
,如果不明白,可以看入门怎么使用 reduce
function getValue(data, expr) { // 先将每个key分隔开,放进数组里 let arr = expr.split("."); // 下面就是reduce的使用了,写出init和fn,就基本ok了 let init = data; let fn = (acc, cur) => acc[cur]; let value = arr.reduce(fn, init); return value; }
然后升级下 compileText
function compileText(data, text) { // 匹配正则 let reg = /\{\{(.+)\}\}/; let newText = text.replace(reg, (...args) => { // 拿到msg let expr = args[1]; // 就改了这里,就能支持多重key let value = getValue(data, expr); return value; }); // 返回替换完的新字符串 return newText; } // demo演示 let data = { person: { name: "hua" } }; let text = "{{person.name}} 喜欢西湖"; text = compileText(data, text); // hua 喜欢西湖 console.log(text);
文本节点编译 <div id='app'> {{msg}} <b>1</b> {{msg}} </div>
先试着将div
里子文本节点里面的表达式正确编译。
let data = { msg: "hello" }; let app = document.querySelector("#app"); // 找到所有子节点 let children = [...app.childNodes]; children.forEach(child => { // 如果是文本节点,就编译 if (child.nodeType === 3) { compileTextNode(child, data); } }); function compileTextNode(node, data) { // 试着写写,就一行代码啦 }
嗯哼,结果如下
function compileTextNode(node, data) { node.textContent = compileText(data, node.textContent); }
元素节点编译 <div id='app'> <span>{{msg}}</span></div>
现在 span 元素里面有的话,怎么编译呢。 其实就是元素节点的话,将其文本元素再挑出来。这里如果深层嵌套,依旧涉及到递归。 可以先试试想想,甚至敲敲compileElement
。
let data = { msg: "hello" }; let app = document.querySelector("#app"); compileElement(app, data); // 增加一个方法,编译元素节点,这里注意,其实只会编译文本节点,如果是元素节点就去找其子文本节点 function compileElement(node, data) { let children = [...node.childNodes]; children.forEach(child => { if (child.nodeType === 1) { compileElement(child, data); } if (child.nodeType === 3) { compileTextNode(child, data); } }); }
避免频繁操作 dom,试试 fragment
这里 app 里面的节点一直在频繁操作,是很不利于性能的。
- 先将 app 里所有的节点放进文档碎片
- 编译完之后,再把文档碎片塞进 app 里,完美。
代码想想也就没啥大的事了
let data = { msg: "hello" }; let app = document.querySelector("#app"); // 塞进碎片 let appFragment = moveToFragment(app); // 编辑碎片 compileElement(appFragment, data); // 碎片在塞进app app.appendChild(appFragment); function moveToFragment(node) { let fragment = document.createDocumentFragment(); // 来试试写这里实现功能 return fragment; }
答案这样子
function moveToFragment(node) { let fragment = document.createDocumentFragment(); // 高阶可以这样 // let firstNode; // while ((firstNode = node.firstChild)) { // fragment.appendChild(firstNode); // } let children = [...node.childNodes]; children.forEach(child => { fragment.appendChild(child); }); return fragment; }
合并写下,编译的整体 demo
可以直接复制,在本地试试。希望里面的方法能有一些启发。
<body> <div id="app"> {{msg}} <div>{{msg}}</div> {{msg}} </div> <script> let data = { msg: "hello" }; let app = document.querySelector("#app"); // 塞进碎片 let appFragment = moveToFragment(app); // 编辑碎片 compileElement(appFragment, data); // 碎片在塞进app app.appendChild(appFragment); function moveToFragment(node) { let fragment = document.createDocumentFragment(); // let firstNode; // while ((firstNode = node.firstChild)) { // fragment.appendChild(firstNode); // } let children = [...node.childNodes]; children.forEach(child => { fragment.appendChild(child); }); return fragment; } function compileElement(node, data) { let children = [...node.childNodes]; children.forEach(child => { if (child.nodeType === 1) { compileElement(child, data); } if (child.nodeType === 3) { compileTextNode(child, data); } }); } function compileTextNode(node, data) { node.textContent = compileText(data, node.textContent); } function compileText(data, text) { // 匹配正则 注释是高级点的匹配 哈哈哈 我还不会 先粘贴在这里 // let reg = /\{\{((?:.|\r?\n)+?)\}\}/g; let reg = /\{\{(.+)\}\}/; let newText = text.replace(reg, (...args) => { // 拿到msg let expr = args[1]; // 就改了这里,就能支持多重key let value = getValue(data, expr); return value; }); // 返回替换完的新字符串 return newText; } function getValue(data, expr) { // 先将每个key分隔开,放进数组里 let arr = expr.split("."); // 下面就是reduce的使用了,写出init和fn,就基本ok了 let init = data; let fn = (acc, cur) => acc[cur]; let value = arr.reduce(fn, init); return value; } </script> </body>