教你用javascript来简单地编写一个页面模板引擎

所以我想我可以写一些简单的代码,与其他现有的logic.absurdjs本身提高模板引擎和工作主要是发表在一个NodeJS模块的形式,但它也释放出的客户端版本。鉴于此,我不能直接使用现有的发动机,因为他们大多运行在Nodejs,不在浏览器上。我需要的是一个小的,纯粹的Javascript编写的东西可以直接运行在浏览器中。当我偶然发现这个博客的另一天John Resig,我惊讶地发现,这不正是我所寻找的。我做了一些微小的变化,和行数约20行。逻辑是很有趣的。在这篇文章中,我将重复步骤写这个引擎步骤过程。如果你能一路看到,你会看到约翰的想法是多么尖锐。

起初我的想法是这样的:


Var TemplateEngine =功能(物流、数据){
魔法/在这里…
}
模板变量=你好,我的名字是。我今年几岁了;
console.log(TemplateEngine(模板,{)
名称:克拉西米尔
年龄:29
});



一个简单的函数,输入的是我们的模板和数据对象,输出的估计你也很容易想到,如下所示:

你好,我叫Krasimir。我29岁。

第一步是查找内部模板参数,然后将其替换为特定的数据到引擎中。我决定用正则表达式来做这个。但是我不擅长这个,所以写的不好的字随时欢迎随时喷涂。


var;



这个正则表达式将捕获所有的结束片段。结束参数G(Global)表示它不仅匹配一个,而且匹配所有符合的片段。我们需要的是根据正则表达式输出数组,其中包含所有字符串,这就是执行所做的。


var;
VaR的比赛= re.exec(TPL);



如果我们打印变量与console.log,我们会看到:


{

名称
指数:21,
输入:
你好,我的名字是。我今年几岁了。
}



但是,我们可以看到返回的数组只包含第一个匹配,我们需要用while循环包装上面的逻辑来获得所有匹配项。


var;
而(赛= re.exec(TPL)){
console.log(比赛);
}



如果你运行上面的代码,你会看到并打印出来。

在这里,有趣的部分即将到来,在识别模板中的匹配项之后,我们必须将它们替换为传递给函数的实际数据。最简单的方法是使用替换函数:


Var TemplateEngine =功能(物流、数据){
var;
而(赛= re.exec(TPL)){
物流= tpl.replace(匹配{ 0 },{ { 1 } }数据匹配)
}
退货物流;
}



好的,它会运行,但它不够好。在这里我们使用一个简单的对象来传输数据,以数据的方式,所以我们稍微改变了数据对象:


{
名称:克拉西米尔Tsonev
简介:{年龄:29 }
}



但它不能直接运行,因为代码将由数据{ 'profile取代。在模板时代},其结果是未定义的,所以我们不能简单地使用替换功能,但在其他方面,最好是使用Javascript代码直接在他们之间,使输入的数据可以直接计算,如下:

复制代码代码如下所示:

模板变量=你好,我的名字是。我今年几岁了;



你可能会好奇,这是怎么发生的这里,约翰使用新函数的语法来创建一个基于字符串的函数:


新函数()
FN(2); /输出3



FN是一个真正的价格函数,它接受一个参数,函数的函数体是console.log(精氨酸+ 1);上面的代码是等效于下面的代码:


函数(精){
console.log(精氨酸+ 1);
}
FN(2); /输出3



这样,我们就可以构造一个基于字符串的函数,包括它的参数和函数体,这并不是我们想要的!但是首先,在构造函数之前,让我们看看函数的主体是什么样子,根据前面的想法,模板引擎最终会返回一个已编译的模板:


返回
你好,我的名字是+
this.name +
。我是+
this.profile.age +
岁;



当然,在实际的模板引擎中,我们会将模板切割成文本和有意义的Javascript代码的小片段。您可能会看到我使用简单的字符串拼接来达到预期的效果,但这并不是我们想要的100%。由于用户可能会传递更复杂的Javascript代码,所以我们需要一个循环,如下所示:




模板变量=
我的技能:+
+
+
';



如果使用字符串拼接,代码应该如下所示:




返回
我的技能:+
对于(此技能中的var索引){ +
+
这是技能{索引} +
+
}



当然,这个代码不能直接运行,运行会出错。所以我使用了约翰文章中写的逻辑,把所有的字符串放在一个数组中,并在程序结束时把它们缝合起来。




var = { };
R.push(我的技能:);
对于(此技能中的var索引){
R.push();
R.push(这个技能{指数});
R.push();
}
返回r.join('');



下一步是收集不同的代码行内的模板,用于生成功能。通过前面介绍的方法,我们可以知道哪些占位符模板中,或它们的位置。所以,依靠辅助变量(光标,光标),我们可以得到想要的结果。




Var TemplateEngine =功能(物流、数据){
var,
代码= 'var R = {};',
游标=0;
var =函数(行){
代码=r.push(line.replace(+ / /克
}
而(赛= re.exec(TPL)){
添加(tpl.slice(光标,匹配。指数));
添加(匹配{ 1 });
光标= match.index +匹配{ 0 }。长度;
}
添加(tpl.substr(光标,tpl.length光标));
代码=返回(r.join ); / /结果返回的;
console.log(代码);
退货物流;
}
模板变量=你好,我的名字是。我今年几岁了;
console.log(TemplateEngine(模板,{)
名称:克拉西米尔Tsonev
简介:{年龄:29 }
});



在上面的代码中的变量的代码保存函数的函数体。开始部分定义一个数组。光标光标告诉我们哪个位置的模板是目前分析。我们需要依靠它来遍历整个模板字符串。此外,还有一个功能,添加,这是负责添加解析的代码行的变量代码。一个地方需要特别注意要躲避(逃避)的双引号字符被编码。否则产生的函数代码会出错。如果我们运行上面的代码,我们会在控制台看到如下:




var = { };
R.push(你好,我的名字是);
R.push(这个名字);
R.push(。我;
R.push(。轮廓。年龄);
返回r.join();



等待,不会看啊,this.name和this.profile.age应该没有行情啊,再次改变。




var =函数(行,js){
JS代码=r.push(+线+ ');':
代码=r.push(line.replace(+ / /克
}
而(赛= re.exec(TPL)){
添加(tpl.slice(光标,匹配。指数));
添加(匹配{ 1 },TRUE);说,这实际上是有效的js
光标= match.index +匹配{ 0 }。长度;
}



占位符的内容以布尔值作为参数传递给Add函数,这个函数用作区分。




var = { };
R.push(你好,我的名字是);
R.push(这个名字);
R.push(。我;
R.push(这个形象。年龄);
返回r.join();



剩下要做的就是创建一个函数并执行它,所以在模板引擎的结尾,替换原来返回给模板字符串的语句如下:

复制代码代码如下:返回新的功能(code.replace( / { r T应用(数据);



我们甚至不需要传递参数给这个功能。我们使用的应用方法来调用它。它会自动设置的函数执行上下文。这就是为什么我们可以在函数中使用this.name。这里指的是数据对象。

模板引擎接近完成,但还有一件事,我们需要支持更复杂的语句,比如条件判断和循环。




模板变量=
我的技能:+
+
+
';
console.log(TemplateEngine(模板,{)
技能:{js
});



会有一个例外,捕获syntaxerror:意想不到的令牌。如果我们调试和代码打印出来的变量,我们可以发现问题。




var = { };
R.push(我的技能:);
R.push(对(VAR指标在这个技能){);
R.push();
R.push(这个技能{指数});
R.push();
R.push(});
R.push();
返回r.join();



for循环的行不应该直接放在数组中,但是应该作为脚本的一部分直接运行。所以在将内容添加到代码变量之前,我们必须做进一步的判断。




var,
reexp =(^)/((如果|为|别的|开关|案例|打破| | { }))( / G),
代码= 'var R = {};',
游标=0;
var =函数(行,js){
JS代码= line.match(reexp):r.push(线+ +行+;':
代码=r.push(line.replace(+ / /克
}



这里我们添加了一个新的正则表达式。它将决定代码是否包含关键字,如如果、为、其他等等。如果有的话,它将直接添加到脚本代码中,否则将被添加到数组中:




var = { };
R.push(我的技能:);
对于(此技能中的var索引){
R.push();
R.push(这个技能{指数});
R.push();
}
R.push();
返回r.join();



当然,编译结果也是正确的。

复制代码代码如下所示:

我的技能:jshtmlcss





最后一个改进可以使我们的模板引擎更强大:




模板变量=
我的技能:+
+
+
+
+
+
没有+
';
console.log(TemplateEngine(模板,{)
技能:{js
showskills:真
});



除了上面提到的改进之外,我还对代码本身进行了优化,最终版本如下:


Var TemplateEngine =功能(HTML、期权){
var = % > / g,reexp = / } +)(^)/((如果|为|别的|开关|案例|打破| | { }))(。*)/ G代码= 'var R = {};游标= 0;
var =函数(行,js){
JS(代码= line.match(reexp):r.push(线+ +行+;'):
(代码=线=r.push!(+(line.replace / / g,' ' ));:;
返回添加;
}
而(赛= re.exec(HTML)){
添加(html.slice(光标,匹配。指数))(匹配{ 1 },真的);
光标= match.index +匹配{ 0 }。长度;
}
添加(html.substr(光标,html.length光标));
代码=返回(r.join );;
返回新的功能(code.replace( / { r T应用(选项);
}



代码比我预期的要少,只有15行。