簡單模板模式是經過格式化字符串拼接出視圖避免建立視圖時大量的節點操做,簡單模板模式不屬於一般定義的設計模式範疇。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