模板是什么?前端模板怎么实现?很多朋友可能不太了解这个,下面这篇文章就给大家介绍一下前端模板的原理和简单的实现代码。
前端模板的开发
模板可以说是前端开发最常用的工具之一。将页面的固定内容提取到一个模板中,将服务器返回的动态数据填充到模板中的预留坑中。最后,组装一个完整的页面html字符串,并交给浏览器进行解析。
模板可以大大提高开发效率。如果没有模板,开发人员将不得不手动拼写字符串。
var tpl = & # 8216& ltp & gt’+用户名+‘& lt/p >’;
$(‘body & # 8217).追加(第三方物流);
在近几年的前端开发过程中,模板也发生了变化:
1。php模板JSP模板
早期没有前端分离的时代,前端只是后端项目中的一个文件夹。在这个时期,php和java提供了自己的模板引擎。以jsp为例:java web应用的页面通常是。JSP文件,内容大部分是html和一些模板自己的语法。它们本质上是纯文本,但既不是html也不是java。
语法:index.jsp
& lthtml & gt
& lthead & gt& lttitle & gtHello World & lt/title >& lt/head >
& ltbody & gt
你好世界!& ltbr/>;
& lt%
out . println(“你的IP地址是”+request . getremote addr());
% & gt
& lt/body >
& lt/html >
在这个时期,模板引擎往往是服务器编译模板字符串并生成html字符串给客户端。
2。八字胡通用模板
随着2009年node的发布,JavaScript也可以实现服务器的功能,大大方便了开发者。小胡子和车把模板的诞生方便了前端开发者。这两个模板都是由JavaScript实现的。从此,前端模板可以同时在服务器和客户端上运行。然而,在大多数使用场景中,js根据服务器异步获取的数据嵌入模板,以生成新的dom插入页码。对前端和后端开发都非常有利。
Mustache语法:index.mustache
& ltp & gt用户名:{ { user.name } } & lt/p >
{{#if (user.gender === 2)}}
& ltp & gt女性
{{/if}}
3。React里的JSX,Vue里的模板
接下来,在新一代中,vue中的模板编写与之前的模板有所不同,功能更加强大。在客户端和服务器端都可以使用,但是在使用场景上有很大的差距:页面根据数据变化,模板生成的dom也随之变化,这对模板的性能要求很高。
Vue语法:index.vue
& ltp & gt用户名:{ { user.name } } & lt/p >
& lt模板v-if = ”user.gender = = = 2 & # 8243& gt
& ltp & gt女性
& lt/div >
由模板实现的功能
无论是从JSP到vue的模板,模板的语法越来越简单,功能越来越丰富,但基本功能是不可或缺的:
变量输出(转义/不转义):出于安全考虑,模板基本默认都会将变量的字符串转义输出,当然也实现了不转义输出的功能,慎重使用。条件判断(if else):开发中经常需要的功能。循环变量:循环数组,生成很多重复的代码片段。模板嵌套:有了模板嵌套,可以减少很多重复代码,并且嵌套模板集成作用域。
以上功能基本涵盖了大部分模板的基本功能。对于这些基本功能,我们可以探讨如何实现模板。
模板实现原理
正如标题所说,模板本质上是纯文本字符串。字符串如何操作js程序?
模板用法:
var DOM string = template(template string,data);
该引擎获取模板字符串和模板的范围,并编译它以生成完整的DOM字符串。
大多数模板具有基本相同的实现原理:
首先,模板通过各种手段剥离公共串和模板语法串,生成抽象语法树AST;然后编译模板语法片段,期间在引擎输入的变量中搜索模板变量;模板片段生成一个普通的html片段,与原来的普通字符串拼接输出。
其实模板编译逻辑并不是特别复杂。至于vue这种动态绑定数据的模板,有时间可以参考文末的链接。
实现快速简单的模板。
现在以小胡子模板为例,手动实现一个实现基本功能的模板。
模板字符串template: index.txt
& lt!DOCTYPE html & gt
& lthtml & gt
& lthead & gt
& ltmeta charset = & # 8221utf-8 ″/>
& ltmeta http-equiv = ”X-UA兼容”内容= ”IE = edge & # 8221& gt
& lttitle & gt页面标题& lt/title >
& ltmeta name = & # 8221viewport & # 8221内容= ”width =设备宽度,initial-scale = 1 ″& gt
& ltlink rel = & # 8221样式表”type = & # 8221text/CSS ”media = & # 8221屏幕”href = & # 8221***in.css & # 8221/>
& lt脚本src = & # 8221***in.js & # 8221& gt& lt/script >
& lt/head >
& ltbody & gt
& lth1 & gt熊猫模板编译
& lth2 & gt普通变量输出
& ltp & gt用户名:{ { common.username } } & lt/p >
& ltp & gtescape:{ { common . escape } } </p >
& lth2 & gt不要转义输出
& ltp & gtunescape:{ { &common.escape } } & lt/p >
& lth2 & gt输出:
& ltul & gt
{ { #每个列表}}
& lt李班= ”{ { value } } & # 8221& gt{ { key } } & lt/李& gt
{{/each}}
& lt/ul >
& lth2 & gt输出条件:
{ { # if shouldEscape } }
& ltp & gtescape { { common.escape } } & lt/p >
{{else}}
& ltp & gtunescape:{ { &common.escape } } & lt/p >
{{/if}}
& lt/body >
& lt/html >
模板对应数据:
模块.导出= {
常见:{
用户名:‘澳大利亚’,
逃:‘& ltp & gtAus & lt/p >’
},
肩景:假,
列表:[
{ key:‘一’,值:1},
{ key:‘b & # 8217,值:2},
{ key:‘c & # 8217,值:3},
{ key:‘d & # 8217,值:4}
]
};
如何使用模板:
var fs = require(“fs & # 8221);
var TPL = fs . read file sync(‘。/index . txt ’, ‘utf8 & # 8217);
var state = require(‘。/test ’);
var Panda = require(‘。/熊猫’);
Panda.render(第三方物流,州)
然后实现模板:
1.常规切割线
模板引擎得到模板字符串后,通常会使用规则切割字符串来区分哪些是静态字符串,哪些是需要编译的字符串,并生成抽象语法树(AST)。
//将未处理的字符串分割成字符组标记。
panda . prototype . parse = function(TPL){
var令牌=[];
var TPL start = 0;
var tagStart = 0;
var tagEnd = 0;
while(tag start >= 0) {
tagStart = tpl.indexOf(openTag,TPL start);
if(tag start <0)破;
//纯文本
tokens.push(新令牌(‘正文’,tpl.slice(tplStart,tag start)));
tagEnd = tpl.indexOf(closeTag,tag start)+2;
if(tagEnd <0)抛出新错误(‘{{}}标签未关闭’);
//细分js
var TPL value = TPL . slice(tag start+2,tagEnd & # 82112);
var token = this . classify js(TPL value);
tokens.push(令牌);
tplStart = tagEnd
}
//最后一段
tokens.push(新令牌(‘正文’,tpl.slice(tagEnd,TPL . length)));
返回this.parseJs(令牌);
};
在这一步中,字符串的分割通常是通过正则化来完成的,后期对字符串的检索会用到很多正则化方法。
在这个步骤中,通常可以检查模板标签的异常关闭并报告一个错误。
2.模板语法的分类
AST生成后,普通字符串不再需要管理,最后会直接输出,重点是模板语法的分类。
//专门处理模板中的js
panda . prototype . parse js = function(tokens){
var sections =[];
var nested tokens =[];
var conditions array =[];
var collector = nestedTokens
var部分;
var电流条件;
for(var I = 0;我& lttokens.lengthi++) {
var token = tokens[I];
var value = token.value
var symbol = token.type
开关(符号){
案例‘#’: {
collector.push(令牌);
sections . push(token);
if(token . action = = = ‘每个’){
collector = token . children =[];
} else if(token . action = = = ‘如果’) {
currentCondition =值;
var conditionArray
collector = condition array =[];
token . conditions = token . conditions | | conditions array;
conditionsArray.push({
条件:当前条件,
收藏家:收藏家
});
}
打破;
}
案例‘else & # 8217: {
if(sections . length = = = 0 | | sections[sections . length –1].开拍!== ‘如果’) {
抛出新错误(‘Else用法错误’);
}
currentCondition =值;
收集器=[];
conditionsArray.push({
条件:当前条件,
收藏家:收藏家
});
打破;
}
案例‘/’: {
section = sections . pop();
如果(第& amp& amp开始行动。== token.value) {
抛出新错误(‘说明标签没有关闭’);
}
if(sections . length >0){
var last section = sections[sections . length –1];
if(last section . action = = = ‘每个’){
collector = last section . chid lren;
} else if(last section . action = ‘如果’) {
conditions array =[];
collector = nestedTokens
}
}否则{
collector = nestedTokens
}
打破;
}
默认值:{
collector.push(令牌);
打破;
}
}
}
返回nestedTokens
}
在最后一步中,我们生成了ast,这里这个AST是一个分词标记数组:
[
令牌{},
令牌{},
令牌{},
]
这个令牌是每个字符串,记录了令牌的类型、动作、子令牌、条件令牌等信息。
/**
* token类代表每个分词的标准数据结构。
*/
函数令牌(类型、值、操作、子代、条件){
this.type = type
this.value = value
this.action = action
this.children =儿童;
条件=条件;
}
在这个步骤中,循环方法中的子令牌被嵌套到相应的令牌中,条件渲染子令牌被嵌套到相应的令牌中。
这一步完成后,一个具有嵌套关系的标准AST就完成了。
3.变量搜索和赋值
现在开始根据token中的变量寻找对应的值,根据对应的函数生成值串。
/**
*解析数据结构的类
*/
函数上下文(数据,父上下文){
this.data = data
this.cache = { & # 8216。’:this . data };
this.parent = parentContext
}
context . prototype . push = function(data){
返回新的上下文(数据,这个);
}
//根据字符串名称找到实变量值
Context.prototype.lookup =函数查找(名称){
name = trim(名称);
var cache = this.cache
var值;
//缓存已被查询
if(cache . hasownproperty(name)){
value = cache[name];
}否则{
var context = this,names,index,lookupHit = false
while(上下文){
//用户.用户名
if(name . index of(‘。’)& gt0) {
value = context.data
names = name . split(‘。’);
索引= 0;
而(值!= null & amp& ampindex & ltnames.length) {
if(index = = = names . length –1) {
lookupHit = hasProperty(value,names[index]);
}
value = value[names[index++]];
}
}否则{
value = context . data[name];
lookup hit = has property(context . data,name);
}
如果(lookupHit) {
打破;
}
context = context.parent
}
cache[name]= value;
}
返回值;
}
为了提高搜索效率,采用了缓存代理,每次找到的可变存储路径便于下次快速搜索。
与JavaScript编译器不同,模板引擎在找不到对应变量时会停止搜索,并无错误地返回空。
4.节点的条件呈现和嵌套
这里先说模板语法令牌和普通字符串令牌,开始统一编译生成字符串,拼接成完整的字符串。
//根据令牌和上下文混合拼接字符串输出结果。
panda . prototype . render tokens = function(token,context) {
var结果= ”;
var令牌、符号、值;
for (var i = 0,numTokens = tokens.length我& ltnumTokens++i) {
值=未定义;
token = tokens[I];
symbol = token.type
if(symbol = = = ‘#’)value = this.renderSection(token,context);
else if(symbol = = = ‘& amp’)value = this . unescapedvalue(token,context);
else if(symbol = = = ‘=’)value = this.escapedValue(token,context);
else if(symbol = = = ‘正文’)value = this . raw value(token);
如果(值!==未定义)结果+=值;
}
返回结果;
}
5.绘制页面
页面字符串已经被解析,可以直接输出:
panda . prototype . render = function(TPL,state) {
if(类型tpl!== ‘字符串’) {
返回新错误(‘请输入一个字符串!’);
}
//解析字符串
var token = this . cache[TPL]?tokens:this . parse(TPL);
//解析数据结构
var context =上下文的状态实例?状态:新上下文(状态);
//呈现模板
返回this.renderTokens(令牌,上下文);
};
浏览器解析页面的输出字符串,然后页面出现。
以上只是一个简单的模板实现,没有经过系统测试,仅供学习使用,源码入口。成熟的模板引擎具有完整的异常处理、变量搜索和分析、范围替换、优化渲染、断点调试等功能。
摘要
前端模板可以做的事情有很多。很多框架集成了模板的功能,配合css、js等混合编译,生成解析好的样式,绑定成功事件的dom。
此外,还有许多方法可以实现模板。本文的实现参考了小胡子源代码。对模板标签中的代码进行解析,但是通过代码片段分类和变量搜索来执行,把纯字符串的代码变成解释器执行的代码。
另外可以看看vue,一个可以实现双向绑定的模板,空。
本文来自吃鸡只用平底锅投稿,不代表舒华文档立场,如若转载,请注明出处:https://www.chinashuhua.cn/24/530265.html