Die Vorlage trennt Daten und Präsentation, wodurch die Präsentationslogik und -effekte leichter verwaltet werden können. Erstellen Sie mithilfe des Funktionsobjekts von JavaScript Schritt für Schritt eine äußerst einfache Vorlagenkonvertierungs-Engine
Vorlageneinführung
Vorlage bezieht sich normalerweise auf Text, der in eine Art dynamischen Programmiersprachencode eingebettet ist. Daten und Vorlagen können in irgendeiner Form kombiniert werden, um unterschiedliche Ergebnisse zu erzielen. Zur Definition der Anzeigeform werden in der Regel Vorlagen verwendet, wodurch die Datenpräsentation umfangreicher und einfacher zu pflegen sein kann. Hier ist beispielsweise ein Beispiel für eine Vorlage:
<ul> <% for(var i in items){ %> <li class='<%= items[i].status %>'><%= items[i].text %></li> <% } %> </ul>
Wenn die folgenden Artikeldaten vorhanden sind:
items:[ { text: 'text1' ,status:'done' }, { text: 'text2' ,status:'pending' }, { text: 'text3' ,status:'pending' }, { text: 'text4' ,status:'processing' } ]
Durch irgendeine Kombination kann der folgende HTML-Code generiert werden:
<ul> <li class='done'>text1<li> <li class='pending'>text2<li> <li class='pending'>text3<li> <li class='processing'>text4<li> </ul>
Wenn Sie den gleichen Effekt ohne Verwendung einer Vorlage erzielen möchten, also die oben genannten Daten als Ergebnis anzeigen möchten, müssen Sie Folgendes tun:
var temp = '<ul>'; for(var i in items){ temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>"; } temp += '</ul>';
Es zeigt sich, dass die Verwendung von Vorlagen folgende Vorteile hat:
Vereinfachtes HTML-Schreiben
Mehr Kontrolle über die Darstellung von Daten durch Programmierelemente (wie Schleifen und bedingte Verzweigungen)
Trennt Daten und Anzeige, wodurch die Anzeigelogik und -effekte einfacher zu warten sind
Template Engine
Ein Programm, das Daten und Vorlagen kombiniert, um das Endergebnis durch die Analyse von Vorlagen auszugeben, wird als Vorlagen-Engine bezeichnet. Es gibt viele Arten von Vorlagen und es gibt viele entsprechende Vorlagen-Engines. Eine ältere Vorlage heißt ERB und wird in vielen Web-Frameworks wie ASP.NET, Rails usw. verwendet. Das obige Beispiel ist ein Beispiel für ERB. Es gibt zwei Kernkonzepte in ERB: Auswerten und Interpolieren. Oberflächlich betrachtet bezieht sich „evaluieren“ auf den Teil, der in <% %> enthalten ist, und „interpolieren“ bezieht sich auf den Teil, der in <%= %> enthalten ist. Aus Sicht der Template-Engine wird der Teil in „evaluieren“ nicht direkt an das Ergebnis ausgegeben und im Allgemeinen zur Prozesssteuerung verwendet, während der Teil in „interpolieren“ direkt an das Ergebnis ausgegeben wird.
Aus Sicht der Implementierung der Template-Engine muss diese auf die dynamischen Kompilierungs- oder dynamischen Interpretationsfunktionen der Programmiersprache zurückgreifen, um die Implementierung zu vereinfachen und die Leistung zu verbessern. Beispiel: ASP.NET verwendet die dynamische Kompilierung von .NET, um Vorlagen in dynamische Klassen zu kompilieren, und verwendet Reflektion, um den Code in der Klasse dynamisch auszuführen. Diese Implementierung ist tatsächlich komplizierter, da C# eine statische Programmiersprache ist, aber mit JavaScript können Sie Function verwenden, um eine einfache Template-Engine mit sehr wenig Code zu implementieren. In diesem Artikel wird eine einfache ERB-Vorlagen-Engine implementiert, um die Leistungsfähigkeit von JavaScript zu demonstrieren.
Vorlagentextkonvertierung
Überprüfen Sie im obigen Beispiel den Unterschied zwischen der Verwendung von Vorlagen und der Nichtverwendung von Vorlagen:
Vorlagenschreiben:
<ul> <% for(var i in items){ %> <li class='<%= items[i].status %>'><%= items[i].text %></li> <% } %> </ul>
Schreiben ohne Vorlage:
var temp = '<ul>'; for(var i in items){ temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>"; } temp += '</ul>';
Bei genauem Hinsehen sind die beiden Methoden tatsächlich sehr „ähnlich“ und können in gewissem Sinne einer Eins-zu-Eins-Entsprechung gefunden werden. Wenn der Text der Vorlage zur Ausführung in Code umgewandelt werden kann, kann eine Vorlagenkonvertierung erreicht werden. Beim Konvertierungsprozess gibt es zwei Prinzipien:
Wenn Sie auf gewöhnlichen Text stoßen, wird dieser direkt zu Zeichenfolgen verkettet
Bei Interpolation (d. h. <%= %>) wird der Inhalt als Variable behandelt und in die Zeichenfolge
eingefügt
Wenn eine Auswertung auftritt (z. B. <% %>), wird sie direkt als Code
behandelt
Transformieren Sie das obige Beispiel gemäß den oben genannten Prinzipien und fügen Sie eine allgemeine Funktion hinzu:
var template = function(items){ var temp = ''; //开始变换 temp += '<ul>'; for(var i in items){ temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>"; } temp += '</ul>'; }
Führen Sie abschließend diese Funktion aus und übergeben Sie die Datenparameter:
var result = template(items);
Dynamische Javascript-Funktion
Es ist ersichtlich, dass die obige Konvertierungslogik eigentlich sehr einfach ist, das Hauptproblem jedoch darin besteht, dass sich die Vorlage ändert, was bedeutet, dass der generierte Programmcode auch zur Laufzeit generiert und ausgeführt werden muss. Glücklicherweise verfügt JavaScript über viele dynamische Funktionen, darunter Funktionen. Normalerweise verwenden wir das Schlüsselwort function, um Funktionen in js zu deklarieren, und Function wird selten verwendet. In js ist die Funktion eine Literalsyntax. Die Laufzeit von js konvertiert die Literalfunktion in ein Funktionsobjekt, sodass die Funktion tatsächlich einen einfacheren und flexibleren Mechanismus bietet.
Die Syntax zum direkten Erstellen einer Funktion mithilfe der Function-Klasse lautet wie folgt:
var function_name = new Function(arg1, arg2, ..., argN, function_body)
Zum Beispiel:
//创建动态函数 var sayHi = new Function("sName", "sMessage", "alert(\"Hello \" + sName + sMessage);"); //执行 sayHi('Hello','World');
Sowohl Funktionskörper als auch Parameter können durch Zeichenfolgen erstellt werden! So cool! Mit dieser Funktion kann der Vorlagentext in eine Zeichenfolge mit Funktionskörper umgewandelt werden, sodass dynamische Funktionen erstellt und dynamisch aufgerufen werden können.
Umsetzungsideen
Verwenden Sie zunächst reguläre Ausdrücke, um die Interpolation und Auswertung zu beschreiben, und Klammern werden zur Gruppenerfassung verwendet:
var interpolate_reg = /<%=([\s\S]+?)%>/g; var evaluate_reg = /<%([\s\S]+?)%>/g;
Um die gesamte Vorlage kontinuierlich abzugleichen, werden diese beiden regulären Ausdrücke zusammengeführt. Beachten Sie jedoch, dass alle Zeichenfolgen, die mit „interpolate“ übereinstimmen können, mit „evalue“ übereinstimmen können. Daher muss „interpolate“ eine höhere Priorität haben:
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>/g
Entwerfen Sie eine Funktion zum Konvertieren von Vorlagen. Die Eingabeparameter sind Vorlagentextzeichenfolgen und Datenobjekte
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>/g //text: 传入的模板文本字串 //data: 数据对象 var template = function(text,data){ ... }
使用replace方法,进行正则的匹配和“替换”,实际上我们的目的不是要替换interpolate或evaluate,而是在匹配的过程中构建出“方法体”:
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>/g //text: 传入的模板文本字串 //data: 数据对象 var template = function(text,data){ var index = 0;//记录当前扫描到哪里了 var function_body = "var temp = '';"; function_body += "temp += '"; text.replace(matcher,function(match,interpolate,evaluate,offset){ //找到第一个匹配后,将前面部分作为普通字符串拼接的表达式 function_body += text.slice(index,offset); //如果是<% ... %>直接作为代码片段,evaluate就是捕获的分组 if(evaluate){ function_body += "';" + evaluate + "temp += '"; } //如果是<%= ... %>拼接字符串,interpolate就是捕获的分组 if(interpolate){ function_body += "' + " + interpolate + " + '"; } //递增index,跳过evaluate或者interpolate index = offset + match.length; //这里的return没有什么意义,因为关键不是替换text,而是构建function_body return match; }); //最后的代码应该是返回temp function_body += "';return temp;"; }
至此,function_body虽然是个字符串,但里面的内容实际上是一段函数代码,可以用这个变量来动态创建一个函数对象,并通过data参数调用:
var render = new Function('obj', function_body); return render(data);
这样render就是一个方法,可以调用,方法内部的代码由模板的内容构造,但是大致的框架应该是这样的:
function render(obj){ var temp = ''; temp += ... ... return temp; }
注意到,方法的形参是obj,所以模板内部引用的变量应该是obj:
<script id='template' type='javascript/template'> <ul> <% for(var i in obj){ %> <li class="<%= obj[i].status %>"><%= obj[i].text %></li> <% } %> </ul> </script>
看似到这里就OK了,但是有个必须解决的问题。模板文本中可能包含\r \n \u2028 \u2029等字符,这些字符如果出现在代码中,会出错,比如下面的代码是错误的:
temp += ' <ul> ' + ... ;
我们希望看到的应该是这样的代码:
temp += '\n \t\t<ul>\n' + ...;
这样需要把\n前面的转义成\即可,最终变成字面的\\n。
另外,还有一个问题是,上面的代码无法将最后一个evaluate或者interpolate后面的部分拼接进来,解决这个问题的办法也很简单,只需要在正则式中添加一个行尾的匹配即可:
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g;
相对完整的代码
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g //模板文本中的特殊字符转义处理 var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; var escapes = { "'": "'", '\\': '\\', '\r': 'r', '\n': 'n', '\t': 't', '\u2028': 'u2028', '\u2029': 'u2029' }; //text: 传入的模板文本字串 //data: 数据对象 var template = function(text,data){ var index = 0;//记录当前扫描到哪里了 var function_body = "var temp = '';"; function_body += "temp += '"; text.replace(matcher,function(match,interpolate,evaluate,offset){ //找到第一个匹配后,将前面部分作为普通字符串拼接的表达式 //添加了处理转义字符 function_body += text.slice(index,offset) .replace(escaper, function(match) { return '\\' + escapes[match]; }); //如果是<% ... %>直接作为代码片段,evaluate就是捕获的分组 if(evaluate){ function_body += "';" + evaluate + "temp += '"; } //如果是<%= ... %>拼接字符串,interpolate就是捕获的分组 if(interpolate){ function_body += "' + " + interpolate + " + '"; } //递增index,跳过evaluate或者interpolate index = offset + match.length; //这里的return没有什么意义,因为关键不是替换text,而是构建function_body return match; }); //最后的代码应该是返回temp function_body += "';return temp;"; var render = new Function('obj', function_body); return render(data); }
调用代码可以是这样:
<script id='template' type='javascript/template'> <ul> <% for(var i in obj){ %> <li class="<%= obj[i].status %>"><%= obj[i].text %></li> <% } %> </ul> </script> ... var text = document.getElementById('template').innerHTML; var items = [ { text: 'text1' ,status:'done' }, { text: 'text2' ,status:'pending' }, { text: 'text3' ,status:'pending' }, { text: 'text4' ,status:'processing' } ]; console.log(template(text,items));
可见,我们只用了很少的代码就实现了一个简易的模板。
遗留的问题
还有几个细节的问题需要注意: