模板语法的简单实现

模板语法的简单实现

模板语法允许在HTML中之插入Js变量以及表达式,当在Js中控制render的时候能够自动在页面上将变量或者是表达式进行计算并显示,比较常见的模板语法有mustcache风格的{{}}以及DSL风格的dsl-html等。

AST#

AST抽象语法树,全称为Abstract Syntax Tree是源代码的抽象语法结构的树状表现形式,每种源码都可以被抽象成为AST,在这里把模板解析成为AST,就是将模板的HTML结构进行解析,变成一棵附带结构、关系、属性的抽象树,这样做方便后续对模板进行处理,减少了多次解析字符串带来的性能消耗,同时将HTML变成一棵树的数据结构之后更加方便于遍历,下面是对于例子中的HTML的简单的AST

Copy
<div class="root" name="root"> <p>1</p> <div>11</div> </div>
Copy
{ type: "tag", tagName: "div", attr: { className: "root" name: "root" }, parent: null, children: [{ type: "tag", tagName: "p", attr: {}, parent: {} /* 父节点的引用 */, children: [{ type: "text", tagName: "text", parent: {} /* 父节点的引用 */, content: "1" }] },{ type: "tag", tagName: "div", attr: {}, parent: {} /* 父节点的引用 */, children: [{ type: "text", tagName: "text", parent: {} /* 父节点的引用 */, content: "11" }] }] }

简单实现#

mustcache风格的{{}}进行简单的实现,仅对于其数据的展示方面有实现,对于其指令例如循环等并未实现,通过处理字符串,将其转换为一个函数并传参执行,即可实现数据的展示。

Copy
<!DOCTYPE html> <html> <head> <title>模板语法</title> </head> <body> <div id="root"> <div>{{show}}</div> <div>{{description}}</div> </div> </body> <script type="text/javascript"> var data = { show: 1, description: "一个简单的模板语法" }; function render(element, data) { var originString = element.innerHTML; var html = String(originString||'').replace(/"/g,'\\"').replace(/\s+|\r|\t|\n/g, ' ') .replace(/\{\{(.)*?\}\}/g, function(value){ return value.replace("{{",'"+(').replace("}}",')+"'); }) html = `var targetHTML = "${html}";return targetHTML;`; var parsedHTML = new Function(...Object.keys(data), html)(...Object.values(data)); element.innerHTML = parsedHTML; } render(document.getElementById("root"), data); </script> </html>

通过对于字符串的处理并使用Function实现模板语法,如果使用正则表达式进行较为完整的过滤,是完全可以生成较为完善的模板语法的处理的,包括Js的表达式以及自带指令等,如mustcache.jslayui.jslaytpl模块。基于AST的模板语法需要解析HTML成为AST,然后将AST转化为字符串,将字符串作为函数执行,这个过程依旧需要用到Function,下边的例子只是借助了Js取得DOM结构生成的AST,没有自行解析HTML

Copy
<!DOCTYPE html> <html> <head> <title>模板语法</title> </head> <body> <div id="root" class="root-node"> <div>{{show}}</div> <div>{{description}}</div> </div> </body> <script type="text/javascript"> var data = { show: 1, description: "一个简单的模板语法" }; function parseAST(root){ var node = {}; node.parent = null; if(root.nodeName === "#text"){ node.type = "text"; node.tagName = "text"; node.content = root.textContent.replace(/\s+|\r|\t|\n/g, ' ').replace(/"/g,'\\"'); }else{ node.type = "tag"; node.tagName = root.localName; node.children = []; node.attr = {}; Array.prototype.forEach.call(root.attributes, item => node.attr[item.nodeName] = item.nodeValue ); } Array.prototype.forEach.call(root.childNodes, element => { var parsedNode = parseAST(element); parsedNode.parent = root; node.children.push(parsedNode); }); return node; } function render(element, template, data) { html = `var targetHTML = "${template}";return targetHTML;`; var parsedHTML = new Function(...Object.keys(data), html)(...Object.values(data)); element.innerHTML = parsedHTML; } function generateHTMLTemplate(AST){ var template = ""; AST.forEach( node => { if(node.type === "tag"){ template += `<${node.tagName}>`; template += generateHTMLTemplate(node.children); template += `</${node.tagName}>`; }else{ if(node.content.match(/\{\{(.)*?\}\}/)){ var expression = node.content.replace(/\{\{(.)*?\}\}/g, function(value){ return value.replace("{{",'"+(').replace("}}",')+"'); }) template += expression; }else{ template += node.content; } } }) return template; } var root = document.getElementById("root"); var AST = parseAST(root); var template = generateHTMLTemplate([AST]); render(root, template, data); </script> </html>

虽然看起来最后都需要使用Function去处理字符串,而AST还需要解析HTML然后再拼接字符串,增加了计算的时间,但是如果仅仅是完全基于处理字符串的方式实现的模板语法,在数据进行变更时都需要进行render,每次render的时候都需要重新渲染整个DOM,虽然在上边的简单实现中AST也是重新渲染了整个模版,但是现在主流的Js框架例如Vue就是基于AST的方式,首先解析templateAST,然后对于AST进行静态节点标记,用以标记静态的节点进行重用跳过比对,从而进行渲染优化,然后生成虚拟DOM,当数据进行变更时虚拟DOM会进行diff算法的比对,找到数据有变更的节点,然后进行最小化渲染,这样就不需要在数据变更时将整个模板进行渲染,从而增加了渲染的效率。

每日一题#

Copy
https://github.com/WindrunnerMax/EveryDay

参考#

Copy
https://www.cnblogs.com/libin-1/p/6544519.html https://www.cnblogs.com/10manongit/p/12869775.html https://blog.csdn.net/weixin_34321977/article/details/91419022
posted @   WindRunnerMax  阅读(225)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示
CONTENTS