,,只有 20 行的 JavaScript 模板引擎实例详解

,,只有 20 行的 JavaScript 模板引擎实例详解

本文主要介绍了只有20行的JavaScript模板引擎,并结合实例分析了JavaScript模板引擎的实现方法及相关注意事项。有需要的可以参考一下。

目录

前言1。识别片段2的提取。数据填充和逻辑处理。代码优化后记(译者注)。这个例子讲述了JavaScript模板引擎。分享给你,供你参考,如下:

原文链接:仅20行的JavaScript模板引擎

(译者吐槽:只收藏不喜欢是耍流氓)

前言

我还在开发我的JS预处理器AbsurdJS。它原本是一个CSS预处理器,后来扩展成了CSS/HTML预处理器,很快就会支持从JS到CSS/HTML的转换。就像模板引擎一样,它可以生成HTML代码,也就是说,它可以用数据填充模板中的标识片段。

所以希望能写一个能满足自己目前需求的模板引擎。AbsurdJS主要是作为NodeJS的一个模块,但是也可以在客户端使用。出于这个目的,我不能使用市面上现有的模板引擎,因为它们几乎都依赖于NodeJS,很难在浏览器中使用。我需要一个更小的纯JS编写的模板引擎。我浏览了John Resig写的这个博客,似乎这正是我所需要的。我稍微修改了一下代码,将其压缩到20行。

这段代码的运行原理很有意思。在这篇文章中,我将一步步向你展示约翰的绝妙想法。

1、提取标识片段

这是我们开始时将得到的结果:

var TemplateEngine=function(tpl,data) {

//魔法在这里.

}

var template='pHello,我的名字是%name%。我今年%岁了。/p ';

console.log(模板引擎(模板,{

姓名:“Krasimir”,

年龄:29岁

}));

一个简单的函数,将模板和数据作为参数传入。可以想象,我们希望得到以下结果:

费罗,我叫克拉西米。我今年29岁。/p

我们需要做的第一件事是获得%.%的标识片段,然后用来自传入引擎的数据填充它们。我决定使用正则表达式来完成这些功能。规律性不是我的强项,大家就将就一下吧。如果有更好的规律性,欢迎随时问我。

var re=/%([^%])?%/g;

我们将匹配所有以%开头并以%结尾的代码块,结尾的g(全局)表示我们将匹配多个代码块。有许多方法可以用来匹配规律性,但我们所需要的是一个可以加载字符串的数组,这正是exec所做的:

var re=/%([^%])?%/g;

var match=re . exec(TPL);

在控制台console.log(match)中,您可以看到:

[

%name% ',

姓名',

指数:21,

输入:

费罗,我的名字是%name%。我今年%岁了。/p '

]

我们得到了正确的匹配结果,但是正如您所看到的,只有一个徽标片段%name%匹配,因此我们需要一个while循环来获取所有的徽标片段。

var re=/%([^%])?%/g,匹配;

while(match=re.exec(tpl)) {

console . log(match);

}

运行,发现所有的识别碎片都被我们获取了。

2、数据填充与逻辑处理

获得标识片段后,我们需要用数据填充它们。使用。替换方法是最简单的方法:

var TemplateEngine=function(tpl,data) {

var re=/%([^%])?%/g,匹配;

while(match=re.exec(tpl)) {

tpl=tpl.replace(match[0],data[match[1]])

}

退货tpl

}

数据={

姓名:“克拉西米尔措涅夫”,

年龄:29岁

}

好的,正常操作。但显然这还不够。我们目前的数据结构非常简单,但在实际开发中,我们会面临更复杂的数据结构:

{

姓名:“克拉西米尔措涅夫”,

个人资料:{年龄:29 }

}

错误的原因是当我们在模板中输入%profile.age%时,我们的数据['profile.age']']未定义。很明显。替换方法不起作用。我们需要一些其他的方法来将真正的JS代码插入到%和%中,就像下面的例子:

var template='pHello,我的名字是%this.name%。我%this.profile.age%岁了。/p ';

这似乎不可能完成?约翰使用了新功能,即通过字符串创建功能的方法来完成这个功能。举个栗子:

var fn=新函数(' arg ',' console . log(arg 1);');

fn(2);//输出3

Fn是一个实函数,它包含一个参数,函数体是console.log(arg 1)。上述代码等效于以下代码:

var fn=function(arg) {

console . log(arg 1);

}

fn(2);//输出3

有了新函数,我们可以通过字符串创建一个函数,这正是我们所需要的。在创建这样一个函数之前,我们需要构造它的函数体。函数体应该返回一个最终的拼接模板。使用前面的模板字符串,想象这个函数应该返回的结果:

返回

费罗,我的名字是

这个名字。我是

this.profile.age

岁。/p ';

显然,我们把模板分成了文本和JS代码。就像上面的代码一样,我们使用简单的字符串串联来得到最终的结果,但是这种方法并不能100%满足我们的需求,因为这样我们就要处理循环之类的JS逻辑,就像这样:

var模板=

我的技能:'

% for(this . skills中的var索引){% '

a href=''%this.skills[index]%/a '

'%}%';

如果使用字符串连接,结果将如下所示:

返回

我的技能:'

for(this . skills中的var指数){

a href=' ' '

this . skills[索引]

/a '

}

当然,这将是一个错误。所以我决定参考约翰的文章写逻辑。——我将所有字符串放入一个数组中,并在最后将它们拼接起来:

var r=[];

r.push('我的技能:');

for(this . skills中的var指数){

r . push(' a href=' ' ');

r . push(this . skills[index]);

r . push('/a ');

}

返回r . join(“”);

下一个逻辑步骤是整理每一行代码以生成一个函数。我们从模板中提取了一些信息,知道了标识段的内容和位置,所以可以通过一个指针变量(cursor)帮助我们得到最终的结果:

var TemplateEngine=function(tpl,data) {

var re=/%([^%])?%/g,

code=' var r=[];\n ',

cursor=0,匹配;

var add=function(line) {

code=' r . push(' line . replace(/'/g,' \ ' ')' ');\ n ';

}

while(match=re.exec(tpl)) {

add(tpl.slice(cursor,match . index));

add(match[1]);

cursor=match.index match[0]。长度;

}

add(tpl.substr(游标,TPL . length-cursor));

code=' return r . join(');';//-返回结果

console.log(代码);

退货tpl

}

var template='pHello,我的名字是%this.name%。我%this.profile.age%岁了。/p ';

console.log(模板引擎(模板,{

姓名:“克拉西米尔措涅夫”,

个人资料:{年龄:29 }

}));

代码以数组声明开始,并作为整个函数的函数体。正如我所说的,指针变量cursor指示我们在模板中的位置,我们需要它遍历所有字符串并跳过充满数据的段。另外,add函数的任务是将字符串插入到代码变量中,作为构建函数体的处理方法。这里有一个棘手的问题。我们需要跳过标识符%%,否则JS脚本将无效。如果我们直接运行上面的代码,结果将如下:

var r=[];

r.push('pHello,我叫');

r . push(' this . name ');

r.push('。我是’);

r . push(' this . profile . age ');

返回r . join(“”);

哦.这不是我们想要的。不应引用This.name和this.profile.age。让我们改进添加功能:

var add=function(line,js) {

js?code=' r . push(' line ');\n ':

code=' r . push(' line . replace(/'/g,' \ ' ')' ');\ n ';

}

var匹配;

while(match=re.exec(tpl)) {

add(tpl.slice(cursor,match . index));

add(匹配[1],真);//-说这是有效的js

cursor=match.index match[0]。长度;

}

标识片段中的内容将由一个布尔值控制。现在我们有了一个正确的函数体:

var r=[];

r.push('pHello,我叫');

r . push(this . name);

r.push('。我是’);

r . push(this . profile . age);

返回r . join(“”);

我们需要做的下一件事是生成并运行这个函数。在这个模板引擎的最后,我们使用下面的代码,而不是直接返回一个tpl对象:

返回新函数(code.replace(/[\r\t\n]/g,' '))。应用(数据);

我们甚至不需要向函数传递任何参数,因为apply方法已经为我们完成了这一步。它会自动设置作用域,这也是this.name能够运行的原因,这就指向了我们的数据。

3、代码优化

差不多完成了。最后一点,我们需要支持更复杂的表达式,比如if/else表达式和循环。让我们尝试使用相同的示例运行以下代码:

var模板=

我的技能:'

% for(this . skills中的var索引){% '

a href=' # ' % this . skills[index]%/a '

'%}%';

console.log(模板引擎(模板,{

技能:['js ',' html ',' css']

}));

结果将被报告为错误,该错误为未捕获的语法错误:的意外标记。仔细观察,通过代码变量,我们可以找出问题:

var r=[];

r.push('我的技能:');

r . push(for(this . skills中的var index){);

r . push(' a href=' ' ');

r . push(this . skills[index]);

r . push('/a ');

r . push(});

r . push(“”);

返回r . join(“”);

包含for循环的代码不应该压入数组,而应该直接放在脚本中。为了解决这个问题,我们需要在将代码推送到代码变量之前再进行一步判断:

var re=/%([^%])?%/g,

reExp=/(^()?(if | for | else | switch | case | break | { | })(。*)?/g,

code=' var r=[];\n ',

光标=0;

var add=function(line,js) {

js?code=line.match(reExp)?line ' \ n ':' r . push(' line ');\n ':

code=' r . push(' line . replace(/'/g,' \ ' ')' ');\ n ';

}

我们增加了一条新规则。这种规律性的作用是,如果一段JS代码以if,for,else,switch,case,break,|,就直接加到函数体中;如果没有,它将被推入代码变量。下面是修改后的结果:

var r=[];

r.push('我的技能:');

for(this . skills中的var指数){

r . push(' a href=' # ' ');

r . push(this . skills[index]);

r . push('/a ');

}

r . push(“”);

返回r . join(“”);

当然,正确的执行:

我的技能:a href=' # ' js/aa href=' # ' html/aa href=' # ' CSS/a

下一个版本会给我们更强大的功能。我们可能会将更复杂的逻辑放入模板中,就像这样:

var模板=

我的技能:'

%if(this.showSkills) {% '

% for(this . skills中的var索引){% '

a href=' # ' % this . skills[index]%/a '

'%}%'

%} else {% '

p一个/p '

'%}%';

console.log(模板引擎(模板,{

技能:['js ',' html ',' css'],

表演技巧:真的

}));

经过一些细微的优化,最终版本如下:

var template engine=function(html,选项){

var re=/%([^%])?%/g,reExp=/(^()?(if | for | else | switch | case | break | { | })(。*)?/g,code=' var r=[];\n ',游标=0,匹配;

var add=function(line,js) {

js?(code=line.match(reExp)?line ' \ n ':' r . push(' line ');\n '):

(代码=行!='' ?r.push('' line.replace(/'/g,' \ ' ')' ');\ n ':' ');

return add

}

while(match=re.exec(html)) {

add(html.slice(cursor,match.index))(match[1],true);

cursor=match.index match[0]。长度;

}

add(html.substr(cursor,html . length-cursor));

code=' return r . join(');';

返回新函数(code.replace(/[\r\t\n]/g,' '))。应用(选项);

}

优化后的代码甚至不到15行。

后记(译者注)

这是我第一次完整的翻译一篇文章。请原谅我句子中的许多错误和遗漏。以后我会继续努力翻译和分享更多的优秀文章。

因为对前端框架、模板引擎之类的工具特别感兴趣,而且很想学习其中的原理,所以找了一个比较简单的模板引擎进行研究。谷歌看到这篇文章后,觉得很优秀。一步一步的讲解生动而深入,代码也是我自己测试的,正确的得到了文章中描述的结果。

模板引擎的设计思路有很多,本文只是其中之一。它的性能和其他参数都需要测试和改进,仅供学习。

谢谢大家~

感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun来测试上述代码的运行效果。

对更多JavaScript相关内容感兴趣的读者可以查看我们的专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》和《JavaScript数学运算用法总结》。

希望这篇文章对大家的JavaScript编程有所帮助。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: