Home > Web Front-end > JS Tutorial > A brief analysis of the basic implementation methods of JavaScript template engine_Basic knowledge

A brief analysis of the basic implementation methods of JavaScript template engine_Basic knowledge

WBOY
Release: 2016-05-16 15:15:32
Original
1160 people have browsed it

The template separates data and presentation, making the presentation logic and effects easier to maintain. Using JavaScript’s Function object, build an extremely simple template conversion engine step by step

Template Introduction
Template usually refers to text embedded with some kind of dynamic programming language code. Data and templates can be combined in some form to produce different results. Templates are usually used to define the display form, which can make data presentation richer and easier to maintain. For example, here is an example of a template:

<ul>
 <% for(var i in items){ %>
 <li class='<%= items[i].status %>'><%= items[i].text %></li>
 <% } %>
</ul>
Copy after login
Copy after login

If there is the following items data:

items:[
 { text: 'text1' ,status:'done' },
 { text: 'text2' ,status:'pending' },
 { text: 'text3' ,status:'pending' },
 { text: 'text4' ,status:'processing' }
]
Copy after login

By combining it in some way, the following Html code can be generated:

<ul>
 <li class='done'>text1<li>
 <li class='pending'>text2<li>
 <li class='pending'>text3<li>
 <li class='processing'>text4<li>
</ul>
Copy after login

If you want to achieve the same effect without using a template, that is, to display the above data as a result, you need to do the following:

var temp = '<ul>';
for(var i in items){
 temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>";
}
temp += '</ul>';
Copy after login
Copy after login

It can be seen that using templates has the following benefits:

Simplified html writing
Have more control over the presentation of data through programming elements (such as loops and conditional branches)
Separates data and display, making the display logic and effects easier to maintain
Template Engine
A program that combines data and templates to output the final result by analyzing templates is called a template engine. There are many types of templates, and there are many corresponding template engines. An older template is called ERB, which is used in many web frameworks, such as ASP.NET, Rails... The above example is an example of ERB. There are two core concepts in ERB: evaluate and interpolate. On the surface, evaluate refers to the part contained in <% %>, and interpolate refers to the part contained in <%= %>. From the perspective of the template engine, the part in evaluate will not be directly output to the result and is generally used for process control; while the part in interpolate will be directly output to the result.

From the perspective of the implementation of the template engine, it needs to rely on the dynamic compilation or dynamic interpretation features of the programming language to simplify the implementation and improve performance. For example: ASP.NET uses .NET's dynamic compilation to compile templates into dynamic classes, and uses reflection to dynamically execute the code in the class. This implementation is actually more complicated because C# is a static programming language, but using JavaScript you can use Function to implement a simple template engine with very little code. This article will implement a simple ERB template engine to show the power of JavaScript.

Template text conversion
For the above example, review the difference between using templates and not using templates:

Template writing:

<ul>
 <% for(var i in items){ %>
 <li class='<%= items[i].status %>'><%= items[i].text %></li>
 <% } %>
</ul>
Copy after login
Copy after login

Non-template writing:

var temp = '<ul>';
for(var i in items){
 temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>";
}
temp += '</ul>';
Copy after login
Copy after login

Looking carefully, the two methods are actually very "similar" and can be found in a certain sense of one-to-one correspondence. If the text of the template can be turned into code for execution, then template conversion can be achieved. There are two principles in the conversion process:

When encountering ordinary text, it is directly concatenated into strings
When encountering interpolate (i.e. <%= %>), the content is treated as a variable and spliced ​​into the string
When encountering evaluate (i.e. <% %>), it is directly treated as code
Transform the above example according to the above principles and add a general function:

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>';
}
Copy after login

Finally execute this function and pass in the data parameters:

var result = template(items);
Copy after login

javascript dynamic function
It can be seen that the above conversion logic is actually very simple, but the key problem is that the template changes, which means that the generated program code must also be generated and executed at runtime. Fortunately, JavaScript has many dynamic features, one of which is Function. We usually use the function keyword to declare functions in js, and Function is rarely used. In js, function is a literal syntax. The runtime of js will convert the literal function into a Function object, so Function actually provides a more low-level and flexible mechanism.

The syntax for directly creating a function using the Function class is as follows:

var function_name = new Function(arg1, arg2, ..., argN, function_body)
Copy after login

For example:

//创建动态函数 
var sayHi = new Function("sName", "sMessage", "alert(\"Hello \" + sName + sMessage);");
//执行 
sayHi('Hello','World');
Copy after login

Both function body and parameters can be created through strings! So cool! With this feature, the template text can be converted into a string of function body, so that dynamic functions can be created and called dynamically.

Implementation ideas
First use regular expressions to describe interpolate and evaluate, and parentheses are used to group capture:

var interpolate_reg = /<%=([\s\S]+&#63;)%>/g;
var evaluate_reg = /<%([\s\S]+&#63;)%>/g;
Copy after login

In order to continuously match the entire template, these two regular expressions are merged together, but note that all strings that can match interpolate can match evaluate, so interpolate needs to have a higher priority:

var matcher = /<%=([\s\S]+&#63;)%>|<%([\s\S]+&#63;)%>/g
Copy after login

Design a function for converting templates, the input parameters are template text strings and data objects

var matcher = /<%=([\s\S]+&#63;)%>|<%([\s\S]+&#63;)%>/g
//text: 传入的模板文本字串
//data: 数据对象
var template = function(text,data){ ... }
Copy after login

使用replace方法,进行正则的匹配和“替换”,实际上我们的目的不是要替换interpolate或evaluate,而是在匹配的过程中构建出“方法体”:

var matcher = /<%=([\s\S]+&#63;)%>|<%([\s\S]+&#63;)%>/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;";
}
Copy after login

至此,function_body虽然是个字符串,但里面的内容实际上是一段函数代码,可以用这个变量来动态创建一个函数对象,并通过data参数调用:

var render = new Function('obj', function_body);
return render(data);
Copy after login

这样render就是一个方法,可以调用,方法内部的代码由模板的内容构造,但是大致的框架应该是这样的:

function render(obj){
 var temp = '';
 temp += ...
 ...
 return temp;
}
Copy after login

注意到,方法的形参是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>
Copy after login

看似到这里就OK了,但是有个必须解决的问题。模板文本中可能包含\r \n \u2028 \u2029等字符,这些字符如果出现在代码中,会出错,比如下面的代码是错误的:

temp += '
 <ul>
 ' + ... ;
Copy after login

我们希望看到的应该是这样的代码:

temp += '\n \t\t<ul>\n' + ...;
Copy after login

这样需要把\n前面的转义成\即可,最终变成字面的\\n。

另外,还有一个问题是,上面的代码无法将最后一个evaluate或者interpolate后面的部分拼接进来,解决这个问题的办法也很简单,只需要在正则式中添加一个行尾的匹配即可:

var matcher = /<%=([\s\S]+&#63;)%>|<%([\s\S]+&#63;)%>|$/g;
Copy after login

相对完整的代码

var matcher = /<%=([\s\S]+&#63;)%>|<%([\s\S]+&#63;)%>|$/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);
}

Copy after login

调用代码可以是这样:

<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));

Copy after login

可见,我们只用了很少的代码就实现了一个简易的模板。

遗留的问题
还有几个细节的问题需要注意:

  • 因为<%或者%>都是模板的边界字符,如果模板需要输出<%或者%>,那么需要设计转义的办法
  • 如果数据对象中包含有null,显然不希望最后输出'null',所以需要在function_body的代码中考虑null的情况
  • 在模板中每次使用obj的形参引用数据,可能不太方便,可以在function_body添加with(obj||{}){...},这样模板中可以直接使用obj的属性
  • 可以设计将render返回出去,而不是返回转化的结果,这样外部可以缓存生成的函数,以提高性能
Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template