20行实现javascript模板引擎
我仍然在用AbsurdJS预处理器写javascript。起初,这只是一个CSS预处理器,后来我把它扩展为CSS/HTML预处理器。最近,它可以实现javascript到CSS/HTML的转换,因为它可以作为模板引擎来生成HTML。比如,可以用数据填充HTML模板。
然后,我就想写一个简单的模板引擎可以完美地与我当前的开发工作相配合。AbsurdJS主要是作为nodejs模块来发布的,但是它也可以作为客户端使用。有了这种想法,我意识到我不能利用现有的模板引擎。因为现在的大多数模板引擎只是基于nodejs,很难把他们复制到浏览器来使用。我需要一个体积小的用原生JS写的模板引擎。我曾经拜读过John Resig的一篇文章JavaScript Micro-Templating。这好像就是我所需要的。我把里面的代码做了些许变动,使之缩减为20行。我想这脚本的运行机制是非常有趣的。本文中,我一步步地重新创建一个模板引擎,然后你就会体会到来自John的伟大创意。
我们以下面的代码作为开始吧:
- var TemplateEngine = function(tpl, data) {
- // magic here ...
- }
- var template = '<p>Hello, my name is <%name%>. I\'m <%age%> years old.</p>';
- console.log(TemplateEngine(template, {
- name: "Krasimir",
- age: 29
- }));
一个简单的函数,来处理我们的模板与数据对象。
你可能会猜想到,我们最终想要达到的结果就是下面的样子:
- <p>Hello, my name is Krasimir. I'm 29 years old.</p>
首先我们必须处理模板内部的动态语法,然后我们用传递给模板引擎的真实数据来替换这些动态语法。我决定利用正则表达式来实现。正则表达式不是我的强项,所以你可以留言建议给我一个更好的正则表达式。
- var re = /<%([^%>]+)?%>/g;
这样,我们会捕获到分别以“%”开始与结束的分组。“g”(global全局)表示我们得到的不是一个,而是全部匹配到的。采用正则表达式的exec方法可以把匹配到分组的以数组的形式表现:
- var re = /<%([^%>]+)?%>/g; var match = re.exec(tpl);
- console.log(match);
结果:
- [
- "<%name%>",
- " name ",
- index: 21,
- input:
- "<p>Hello, my name is <%name%>. I\'m <%age%> years old.</p>"
- ]
但是我们只得到了一个,再来改善一下:
- var TemplateEngine = function(tpl, data) {
- var re = /<%([^%>]+)?%>/g;
- while(match = re.exec(tpl)) {
- tpl = tpl.replace(match[0], data[match[1]])
- }
- return tpl;
- }
OK,我们的最初目标达到了,但这远远不够。这只能容易获取到data['property']。但在实践中,我们可能遇到复杂的嵌套对象。比如:
- {
- name: "Krasimir Tsonev",
- profile: { age: 29 }
- }
我们先前所做的工作就这样失效了!
那我们就分析一下其他的情况。比如,我们有个这样的模板:
- var template = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.</p>';
再或者我们可能还见过这样的模板:
- var template =
- 'My skills:' +
- '<%for(var index in this.skills) {%>' +
- '<a href=""><%this.skills[index]%></a>' +
- '<%}%>';
这该怎么办呢? John采用了 new Function 语法实现——可以从字符串来创建函数。
那我们先来熟悉一下这种语法。看一个简单的例子:
- var fn = new Function("arg", "console.log(arg + 1);");
- fn(2); // outputs 3
上述代码创建的函数fn等价于:
- function fn(arg){
- console.log(arg+1);
- }
- fn(2) // outputs 3
这样我们可以自定义函数,其参数与函数体可以来自简单的字符串。而我们所需要的方法应该能够返回为最终的编译模板。就像这样:
- return
- "<p>Hello, my name is " +
- this.name +
- ". I\'m " +
- this.profile.age +
- " years old.</p>";
而对于
- var template =
- 'My skills:' +
- '<%for(var index in this.skills) {%>' +
- '<a href=""><%this.skills[index]%></a>' +
- '<%}%>';
我们所需要的应该是这样:
- return
- 'My skills:' +
- for(var index in this.skills) { +
- '<a href="">' +
- this.skills[index] +
- '</a>' +
- }
当然我们所设想的会产生语法错误,那我们可以改变一下:
- var r = [];
- r.push('My skills:');
- for(var index in this.skills) {
- r.push('<a href="">');
- r.push(this.skills[index]);
- r.push('</a>');
- }
- return r.join('');
有了预想结果,下一步就是分别处理每一行来产生自定义函数。
在进行处理每一行的时候,我们应该考虑到以下问题:
- 引号的转义,否则产生的脚本不可用
- <% %>里面的字符不应该被当做字符串处理
- 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].length;
- }
- add(tpl.substr(cursor, tpl.length - cursor));
- code += 'return r.join("");'; // <-- return the result
- console.log(code);
- return tpl;
- }
- var template = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.</p>';
- console.log(TemplateEngine(template, {
- name: "Krasimir Tsonev",
- profile: { age: 29 }
- }));
考虑到if/else等JS语句,我们再做优化:
- var TemplateEngine = function(html, options) {
- var re = /<%([^%>]+)?%>/g, reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = 'var r=[];\n', cursor = 0;
- var add = function(line, js) {
- js? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
- (code += line != '' ? '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].length;
- }
- add(html.substr(cursor, html.length - cursor));
- code += 'return r.join("");';
- return new Function(code.replace(/[\r\t\n]/g, '')).apply(options);
- }
终极目标实现!!
详见最终版本
原文作者:http://krasimirtsonev.com/blog/article/Javascript-template-engine-in-just-20-line
相关推荐
基于javascript 实现的模板引擎,类似于 Microsoft’s jQuery template plugin,但更简单易用! 标签:Mustache
laytpl是一款颠覆性的JavaScript模板引擎,它用巧妙的实现方式,将自身的体积变得小巧玲珑,不仅性能接近极致,并且还具备传统js引擎的几乎所有功能。所有的变身魔法都由不到2KB的代码创造,laytpl视图用最轻量的...
主要介绍了只有 20 行的 JavaScript 模板引擎,结合实例形式分析了JavaScript 模板引擎实现方法与相关注意事项,需要的朋友可以参考下
Velocity 是基于Java的模板引擎,广泛应用在阿里集 体各个子公司。Velocity模板适用于大量模板使用的场景,支持复杂的逻辑运算,包含 基本数据类型、变量赋值和函数等功能。Velocity.js 支持 Node.js 和浏览器环境。...
laytpl是一款颠覆性的JavaScript模板引擎,它用巧妙的实现方式,将自身的体积变得小巧玲珑,不仅性能接近极致,并且还具备传统前端引擎的几乎所有功能。所有的变身魔法都由不到1KB的代码创造,这仿佛是一场革命,又...
淘宝前端UED资料 Juicer 一个Javascript模板引擎的实现和优化)精品
JavaScript的{{mustache}}模板引擎的一个极其快速和小的子实现
artTemplate 是新一代 javascript 模板引擎,它采用预编译方式让性能有了质的飞跃,并且充分利用 javascript 引擎特性,使得其性能无论在前端还是后端都有极其出色的表现。在 chrome 下渲染效率测试中分别是知名引擎...
一段简单的JavaScript实现的前端模板引擎,可以实现简单的变量替代
主要介绍了JavaScript模板引擎实现原理,结合实例形式详细分析了JavaScript模板引擎原理、定义、使用方法及相关操作注意事项,需要的朋友可以参考下
主要介绍了JavaScript模板引擎实现原理详解,本文着重讲解artTemplate模板的实现原理,它采用预编译方式让性能有了质的飞跃,是其它知名模板引擎的25、32 倍,需要的朋友可以参考下
本文实例讲述了JavaScript模板引擎应用场景及实现原理。分享给大家供大家参考,具体如下: 一、应用场景 以下应用场景可以使用模板引擎: 1、如果你有动态ajax请求数据并需要封装成视图展现给用户,想要提高自己的...
Mustache是基于JavaScript实现的模版引擎,类似于JQuery Template,但是这个模版更加的轻量级,语法更加的简单易用,很容易上手。
基于javascript 实现的模板引擎,类似于 Microsoft’s jQuery template plugin,但更简单易用! 2. doT.js doT.js 包含为浏览器和Node.js 准备的 JavaScript 模板引擎。 3. jSmart jSmart 是著名的 PHP 模板引擎 Smarty...
于是便有了这样一个函数:有了这个函数,我们拼接字符串的工作就可以简化为:看到这里,不用我多说,我想通过这个例子直观的展现出前端模板引擎的好处所在,这么做能够完全剥离html和代码逻辑,便于多人协作和后期的...
主要介绍了JavaScript模板引擎用法,涉及javascript实现模板的定义与字符替换的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
艺术模板 |art-template是一个简单且超快速的模板引擎,可通过范围内预先声明的技术优化模板渲染速度,从而实现接近JavaScript极限的运行时性能。 同时,它支持NodeJS和浏览器。 。 art-template是一个简约,超快的...