简单模板模式是经过格式化字符串拼接出视图避免建立视图时大量的节点操做,简单模板模式不属于一般定义的设计模式范畴。javascript
对比于模板方法模式,其定义了如何执行某些算法的框架,经过父类公开的接口或方法子类去实现或者是调用,而简单模板模式是用来解决为了建立视图的大量节点操做,并在此基础上解决数据与结构的强耦合性。html
若是咱们要生成一个列表,直接经过各种节点操做是相对比较麻烦的。java
<!DOCTYPE html> <html> <head> <title>节点操做</title> </head> <body> <div id="root"></div> </body> <script type="text/javascript"> (function(){ const container = document.getElementById("root"); const ul = document.createElement("ul"); const list = [{ "name": "google", "url": "https://www.google.com" }, { "name": "baidu", "url": "https://www.baidu.com" }, { "name": "bing", "url": "https://cn.bing.com" }]; list.forEach(v => { let li = document.createElement("li"); let a = document.createElement("a"); a.href = v.url; a.target = "_blank"; a.innerText = v.name; li.appendChild(a); ul.appendChild(li); }); container.appendChild(ul); })(); </script> </html>
若是咱们使用字符串拼接,虽然可以减小看上去的复杂程度,可是实际因为数据和结构强耦合致使可维护性一般比较差,这致使的问题是若是数据或者结构发生变化时,都须要改变代码。此外此处使用了ES6
的模板字符串语法动态生成了一个ul
列表,看上去貌似不会复杂,若是直接使用字符串拼接,会繁琐不少。node
<!DOCTYPE html> <html> <head> <title>字符串拼接</title> </head> <body> <div id="root"></div> </body> <script type="text/javascript"> (function(){ const container = document.getElementById("root"); const list = [{ "name": "google", "url": "https://www.google.com" }, { "name": "baidu", "url": "https://www.baidu.com" }, { "name": "bing", "url": "https://cn.bing.com" }]; let template = `<ul>`; list.forEach(v => { template += `<li> <a href="${v.url}" target="_blank" >${v.name}</a> </li>`; }); template += "</ul>"; container.innerHTML = template.replace(/[\s]+/g, " "); })(); </script> </html>
经过建立模板,咱们可使用数据去格式化字符串来渲染视图并插入到容器中,这样实现的方案可读性会高不少。git
<!DOCTYPE html> <html> <head> <title>模板渲染</title> </head> <body> <div id="root"></div> </body> <script type="text/javascript"> (function(){ const container = document.getElementById("root"); const formatString = function(str, data){ return str.replace(/\{\{(\w+)\}\}/g, (match, key) => typeof(data[key]) === void 0 ? "" : data[key]); } const list = [{ "name": "google", "url": "https://www.google.com" }, { "name": "baidu", "url": "https://www.baidu.com" }, { "name": "bing", "url": "https://cn.bing.com" }]; let template = ["<ul>"]; list.forEach(v => { template.push("<li>"); template.push(formatString('<a href="{{url}}" target="_blank" >{{name}}</a>', v)); template.push("</li>"); }); template.push("</ul>"); console.log(template) container.innerHTML = template.join(""); })(); </script> </html>
对mustcache
风格的{{}}
进行简单的实现,仅对于其数据的展现方面有实现,对于其指令例如循环等并未实现,经过处理字符串,将其转换为一个函数并传参执行,便可实现数据的展现。经过对于字符串的处理并使用Function
实现模板语法,若是使用正则表达式进行较为完整的过滤,是彻底能够生成较为完善的模板语法的处理的,包括Js
的表达式以及自带指令等,如mustcache.js
、layui.js
的laytpl
模块。github
<!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>
基于AST
的模板语法须要解析HTML
成为AST
,而后将AST
转化为字符串,将字符串做为函数执行,这个过程依旧须要用到Function
,下边的例子只是借助了Js
取得DOM
结构生成的AST
,没有自行解析HTML
。虽然看起来最后都须要使用Function
去处理字符串,而AST
还须要解析HTML
而后再拼接字符串,增长了计算的时间,可是若是仅仅是彻底基于处理字符串的方式实现的模板语法,在数据进行变动时都须要进行render
,每次render
的时候都须要从新渲染整个DOM
,虽然在上边的简单实现中AST
也是从新渲染了整个模版,可是如今主流的Js
框架例如Vue
就是基于AST
的方式,首先解析template
为AST
,而后对于AST
进行静态节点标记,用以标记静态的节点进行重用跳过比对,从而进行渲染优化,而后生成虚拟DOM
,当数据进行变动时虚拟DOM
会进行diff
算法的比对,找到数据有变动的节点,而后进行最小化渲染,这样就不须要在数据变动时将整个模板进行渲染,从而增长了渲染的效率。正则表达式
<!DOCTYPE html> <html> <head> <title>AST</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>
https://github.com/WindrunnerMax/EveryDay
https://juejin.cn/post/6844903633000087560 https://www.cnblogs.com/libin-1/p/6544519.html https://github.com/sentsin/layui/blob/master/src/lay/modules/laytpl.js