Home > Article > Web Front-end > What is a front-end template? Introduction to the principles and examples of front-end templates
What is the front-end template? How to implement front-end template? Many friends may not know much about this, so the following article will introduce to you the principles of front-end templates and simple implementation codes.
Template can be said to be one of the most commonly used tools in front-end development. Extract the fixed content of the page into a template, fill the dynamic data returned by the server into the reserved pits in the template, and finally assemble it into a complete page HTML string and give it to the browser for parsing.
Templates can greatly improve development efficiency. Without templates, developers may have to manually spell strings.
var tpl = '<p>' + user.name + '</p>'; $('body').append(tpl);
In the process of front-end development in recent years, templates have also changed:
1. PHP template JSP template
In the early days, it was still There was no era of separation of front-end and back-end. The front-end was just a folder in the back-end project. During this period, PHP and Java both provided their own template engines. Take JSP as an example: The pages of Java web applications are usually .jsp files. The content of this file is most of HTML and some templates have their own syntax. It is essentially plain text, but it is neither HTML nor Java.
JSP syntax: index.jsp
<html> <head><title>Hello World</title></head> <body> Hello World!<br/> <% out.println("Your IP address is " + request.getRemoteAddr()); %> </body> </html>
The template engine of this period often used the server to compile the template string and generate the html string to the client.
2. handlebar mustache universal template
Node was released in 2009, and JavaScript can also be used to implement server-side functions, which also greatly facilitates developers. The birth of mustache and handlebar templates has facilitated front-end developers. Both templates are implemented using JavaScript. From now on, this front-end template can be run on the server or the client, but most usage scenarios All js is inserted into the template based on the data obtained asynchronously from the server, and a new dom is inserted into the page number. It is very beneficial for both front-end and back-end development.
mustache syntax: index.mustache
<p>Username: {{user.name}}</p> {{#if (user.gender === 2)}} <p>女</p> {{/if}}
3. Template in vue JSX in React
Next comes the new generation, The template writing method in Vue is different from the previous templates, and it is more powerful. It can be used on both the client and the server, but the usage scenarios are very different: the page often changes according to the data, and the DOM generated by the template changes, which requires high performance for the template.
vue syntax: index.vue
<p>Username: {{user.name}}</p> <template v-if="user.gender === 2"> <p>女</p> </div>
Whether it is a template from JSP to vue, templates are becoming more and more simple in syntax and more and more functional. Rich, but the basic functions are indispensable:
Variable output (escaped/unescaped): For security reasons, the template will basically escape the string of the variable and output it by default. , of course, also implements the function of unescaped output, so use it with caution.
Conditional judgment (if else): A function often needed in development.
Loop variables: loop arrays to generate many repeated code snippets.
Template nesting: With template nesting, you can reduce a lot of duplicate code, and nested templates integrate scopes.
The above functions basically cover the basic functions of most templates. For these basic functions, you can explore how the template is implemented.
As the title says, templates are essentially plain text strings. How do strings operate js programs?
Template usage:
var domString = template(templateString, data);
The template engine obtains the template string and the scope of the template, and generates a complete DOM string after compilation.
Most template implementation principles are basically the same:
The template string first separates ordinary strings and template syntax strings through various means to generate an abstract syntax tree AST; then it performs the template syntax fragments During compilation, the template variables are searched in the variables input by the engine; the template syntax fragment generates an ordinary HTML fragment, which is spliced with the original ordinary string for output.
In fact, the template compilation logic is not particularly complicated. As for templates such as Vue that dynamically bind data, you can refer to the link at the end of the article when you have time.
Now take the mustache template as an example to manually implement a template that implements basic functions.
Template string template: index.txt
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Page Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" type="text/css" media="screen" href="main.css" /> <script src="main.js"></script> </head> <body> <h1>Panda模板编译</h1> <h2>普通变量输出</h2> <p>username: {{common.username}}</p> <p>escape:{{common.escape}}</p> <h2>不转义输出</h2> <p>unescape:{{&common.escape}}</p> <h2>列表输出:</h2> <ul> {{#each list}} <li class="{{value}}">{{key}}</li> {{/each}} </ul> <h2>条件输出:</h2> {{#if shouldEscape}} <p>escape{{common.escape}}</p> {{else}} <p>unescape:{{&common.escape}}</p> {{/if}} </body> </html>
Template corresponding data:
module.exports = { common: { username: 'Aus', escape: '<p>Aus</p>' }, shouldEscape: false, list: [ {key: 'a', value: 1}, {key: 'b', value: 2}, {key: 'c', value: 3}, {key: 'd', value: 4} ] };
How to use the template:
var fs = require("fs"); var tpl = fs.readFileSync('./index.txt', 'utf8'); var state = require('./test'); var Panda = require('./panda'); Panda.render(tpl, state)
Then implement the template:
After the template engine obtains the template string, it usually uses regular cut strings to distinguish those that are static strings and those that need to be compiled. , generate an abstract syntax tree (AST).
// 将未处理过的字符串进行分词,形成字符组tokens Panda.prototype.parse = function (tpl) { var tokens = []; var tplStart = 0; var tagStart = 0; var tagEnd = 0; while (tagStart >= 0) { tagStart = tpl.indexOf(openTag, tplStart); if (tagStart < 0) break; // 纯文本 tokens.push(new Token('text', tpl.slice(tplStart, tagStart))); tagEnd = tpl.indexOf(closeTag, tagStart) + 2; if (tagEnd < 0) throw new Error('{{}}标签未闭合'); // 细分js var tplValue = tpl.slice(tagStart + 2, tagEnd - 2); var token = this.classifyJs(tplValue); tokens.push(token); tplStart = tagEnd; } // 最后一段 tokens.push(new Token('text', tpl.slice(tagEnd, tpl.length))); return this.parseJs(tokens); };
This step of splitting the string is usually done using regular expressions. Regular methods will be used extensively when retrieving strings later.
In this step, you can usually check that the template tag is closed abnormally and report an error.
After generating AST, ordinary strings no longer need to be managed, and will be output directly in the end, focusing on the classification of template syntax.
// 专门处理模板中的js Panda.prototype.parseJs = function (tokens) { var sections = []; var nestedTokens = []; var conditionsArray = []; var collector = nestedTokens; var section; var currentCondition; for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; var value = token.value; var symbol = token.type; switch (symbol) { case '#': { collector.push(token); sections.push(token); if(token.action === 'each'){ collector = token.children = []; } else if (token.action === 'if') { currentCondition = value; var conditionArray; collector = conditionArray = []; token.conditions = token.conditions || conditionsArray; conditionsArray.push({ condition: currentCondition, collector: collector }); } break; } case 'else': { if(sections.length === 0 || sections[sections.length - 1].action !== 'if') { throw new Error('else 使用错误'); } currentCondition = value; collector = []; conditionsArray.push({ condition: currentCondition, collector: collector }); break; } case '/': { section = sections.pop(); if (section && section.action !== token.value) { throw new Error('指令标签未闭合'); } if(sections.length > 0){ var lastSection = sections[sections.length - 1]; if(lastSection.action === 'each'){ collector = lastSection.chidlren; } else if (lastSection.action = 'if') { conditionsArray = []; collector = nestedTokens; } } else { collector = nestedTokens; } break; } default: { collector.push(token); break; } } } return nestedTokens; }
In the previous step, we generated an AST. This AST here is an array of word segmentation tokens:
[ Token {}, Token {}, Token {}, ]
This token is each string, which records the token type, action, and sub-tokens respectively. , condition token and other information.
/** * token类表示每个分词的标准数据结构 */ function Token (type, value, action, children, conditions) { this.type = type; this.value = value; this.action = action; this.children = children; this.conditions = conditions; }
在这一步要将循环方法中的子token嵌套到对应的token中,以及条件渲染子token嵌套到对应token中。
这步完成之后,一个标准的带有嵌套关系的AST完成了。
现在开始根据token中的变量查找到对应的值,根据相应功能生成值得字符串。
/** * 解析数据结构的类 */ function Context (data, parentContext) { this.data = data; this.cache = { '.': this.data }; this.parent = parentContext; } Context.prototype.push = function (data) { return new Context(data, this); } // 根据字符串name找到真实的变量值 Context.prototype.lookup = function lookup (name) { name = trim(name); var cache = this.cache; var value; // 查询过缓存 if (cache.hasOwnProperty(name)) { value = cache[name]; } else { var context = this, names, index, lookupHit = false; while (context) { // user.username if (name.indexOf('.') > 0) { value = context.data; names = name.split('.'); index = 0; while (value != null && index < names.length) { if (index === names.length - 1) { lookupHit = hasProperty(value, names[index]); } value = value[names[index++]]; } } else { value = context.data[name]; lookupHit = hasProperty(context.data, name); } if (lookupHit) { break; } context = context.parent; } cache[name] = value; } return value; }
为了提高查找效率,采用缓存代理,每次查找到的变量存储路径方便下次快速查找。
不同于JavaScript编译器,模板引擎在查找变量的时候找不到对应变量即终止查找,返回空并不会报错。
这里开始讲模板语法token和普通字符串token开始统一编译生成字符串,并拼接成完整的字符串。
// 根据tokens和context混合拼接字符串输出结果 Panda.prototype.renderTokens = function (tokens, context) { var result = ''; var token, symbol, value; for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { value = undefined; token = tokens[i]; symbol = token.type; if (symbol === '#') value = this.renderSection(token, context); else if (symbol === '&') value = this.unescapedValue(token, context); else if (symbol === '=') value = this.escapedValue(token, context); else if (symbol === 'text') value = this.rawValue(token); if (value !== undefined) result += value; } return result; }
页面字符串已经解析完成,可以直接输出:
Panda.prototype.render = function (tpl, state) { if (typeof tpl !== 'string') { return new Error('请输入字符串!'); } // 解析字符串 var tokens = this.cache[tpl] ? tokens : this.parse(tpl); // 解析数据结构 var context = state instanceof Context ? state : new Context(state); // 渲染模板 return this.renderTokens(tokens, context); };
输出页面字符串被浏览器解析,就出现了页面。
以上只是简单的模板实现,并没有经过系统测试,仅供学习使用,源码传送门。成熟的模板引擎是有完整的异常处理,变量查找解析,作用域替换,优化渲染,断点调试等功能的。
前端模板这块能做的东西还很多,很多框架都是集成模板的功能,配合css,js等混合编译生成解析好样式和绑定成功事件的dom。
另外实现模板的方式也有很多,本文的实现方式参考了mustache源码,模板标签内的代码被解析,但是是通过代码片段分类,变量查找的方式来执行的,将纯字符串的代码变成了被解释器执行的代码。
另外向vue这种可以实现双向绑定的模板可以抽空多看一看。
相关推荐:
javascript - 大家前端js模板是用underscore还是handlebars呢?
学习前端模板引擎 jade (一)_html/css_WEB-ITnose
The above is the detailed content of What is a front-end template? Introduction to the principles and examples of front-end templates. For more information, please follow other related articles on the PHP Chinese website!