搜索
首页web前端js教程如何使用javascript写模板引擎代码思路和实例详解

AbsurdJS本身主要是以NodeJS的模块的形式发布的,不过它也会发布客户端版本。考虑到这些,我就不能直接使用现有的引擎了,因为它们大部分都是在NodeJS上运行的,而不能跑在浏览器上。

最初的想法是这样子的:


var TemplateEngine = function(tpl, data) {
  // magic here ...
}
var template = &#39;<p>Hello, my name is <%name%>. I\&#39;m <%age%> years old.</p>&#39;;
console.log(TemplateEngine(template, {
  name: "Krasimir",
  age: 29
}));

一个简单的函数,输入是我们的模板以及数据对象,输出么估计你也很容易想到,像下面这样子:

<p>Hello, my name is Krasimir. I&#39;m 29 years old.</p>

其中第一步要做的是寻找里面的模板参数,然后替换成传给引擎的具体数据。我决定使用正则表达式来完成这一步。不过我不是最擅长这个,所以写的不好的话欢迎随时来喷。


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

这句正则表达式会捕获所有以8f0ae009f127e9c9db4c0668b5f71e25结尾的片段。末尾的参数g(global)表示不只匹配一个,而是匹配所有符合的片段。Javascript里面有很多种使用正则表达式的方法,我们需要的是根据正则表达式输出一个数组,包含所有的字符串,这正是exec所做的。


var re = /<%([^%>]+)?%>/g;
var match = re.exec(tpl);

如果我们用console.log把变量match打印出来,我们会看见:


[
  "<%name%>",
  " name ", 
  index: 21,
  input: 
  "<p>Hello, my name is <%name%>. I\&#39;m <%age%> years old.</p>"
]

不过我们可以看见,返回的数组仅仅包含第一个匹配项。我们需要用while循环把上述逻辑包起来,这样才能得到所有的匹配项。


var re = /<%([^%>]+)?%>/g;
while(match = re.exec(tpl)) {
  console.log(match);
}

如果把上面的代码跑一遍,你就会看见db185f51abe1d02301f57d0c9e8c9eb5 和 5775174bcf4feadc65f033b84eb1e107都被打印出来了。

下面,有意思的部分来了。识别出模板中的匹配项后,我们要把他们替换成传递给函数的实际数据。最简单的办法就是使用replace函数。我们可以像这样来写:


var TemplateEngine = function(tpl, data) {
  var re = /<%([^%>]+)?%>/g;
  while(match = re.exec(tpl)) {
    tpl = tpl.replace(match[0], data[match[1]])
  }
  return tpl;
}

好了,这样就能跑了,但是还不够好。这里我们以data["property"]的方式使用了一个简单对象来传递数据,但是实际情况下我们很可能需要更复杂的嵌套对象。所以我们稍微修改了一下data对象:


{
  name: "Krasimir Tsonev",
  profile: { age: 29 }
}

不过直接这样子写的话还不能跑,因为在模板中使用913f7752eb512a3f068e4113f2294efd的话,代码会被替换成data[‘profile.age'],结果是undefined。这样我们就不能简单地用replace函数,而是要用别的方法。如果能够在e4a7fbc82c514fa16c9678a282299263之间直接使用Javascript代码就最好了,这样就能对传入的数据直接求值,像下面这样:

var template = &#39;<p>Hello, my name is <%this.name%>. I\&#39;m <%this.profile.age%> years old.</p>&#39;;

你可能会好奇,这是怎么实现的?这里John使用了new Function的语法,根据字符串创建一个函数。我们不妨来看个例子:


var fn = new Function("arg", "console.log(arg + 1);");
fn(2); // outputs 3

fn可是一个货真价实的函数。它接受一个参数,函数体是console.log(arg + 1);。上述代码等价于下面的代码:


var fn = function(arg) {
  console.log(arg + 1);
}
fn(2); // outputs 3

通过这种方法,我们可以根据字符串构造函数,包括它的参数和函数体。这不正是我们想要的嘛!不过先别急,在构造函数之前,我们先来看看函数体是什么样子的。按照之前的想法,这个模板引擎最终返回的应该是一个编译好的模板。还是用之前的模板字符串作为例子,那么返回的内容应该类似于:


return
"<p>Hello, my name is " + 
this.name + 
". I\&#39;m " + 
this.profile.age + 
" years old.</p>";

  当然啦,实际的模板引擎中,我们会把模板切分为小段的文本和有意义的Javascript代码。前面你可能看见我使用简单的字符串拼接来达到想要的效果,不过这并不是100%符合我们要求的做法。由于使用者很可能会传递更加复杂的Javascript代码,所以我们这儿需要再来一个循环,如下:


var template = 
&#39;My skills:&#39; + 
&#39;<%for(var index in this.skills) {%>&#39; + 
&#39;<a href=""><%this.skills[index]%></a>&#39; +
&#39;<%}%>&#39;;

  如果使用字符串拼接的话,代码就应该是下面的样子:


return
&#39;My skills:&#39; + 
for(var index in this.skills) { +
&#39;<a href="">&#39; + 
this.skills[index] +
&#39;</a>&#39; +
}

  当然,这个代码不能直接跑,跑了会出错。于是我用了John的文章里写的逻辑,把所有的字符串放在一个数组里,在程序的最后把它们拼接起来。


var r = [];
r.push(&#39;My skills:&#39;); 
for(var index in this.skills) {
r.push(&#39;<a href="">&#39;);
r.push(this.skills[index]);
r.push(&#39;</a>&#39;);
}
return r.join(&#39;&#39;);

  下一步就是收集模板里面不同的代码行,用于生成函数。通过前面介绍的方法,我们可以知道模板中有哪些占位符(译者注:或者说正则表达式的匹配项)以及它们的位置。所以,依靠一个辅助变量(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].length;
  }
  add(tpl.substr(cursor, tpl.length - cursor));
  code += 'return r.join("");'; // <-- return the result
  console.log(code);
  return tpl;
}
var template = &#39;<p>Hello, my name is <%this.name%>. I\&#39;m <%this.profile.age%> years old.</p>&#39;;
console.log(TemplateEngine(template, {
  name: "Krasimir Tsonev",
  profile: { age: 29 }
}));

  上述代码中的变量code保存了函数体。开头的部分定义了一个数组。游标cursor告诉我们当前解析到了模板中的哪个位置。我们需要依靠它来遍历整个模板字符串。此外还有个函数add,它负责把解析出来的代码行添加到变量code中去。有一个地方需要特别注意,那就是需要把code包含的双引号字符进行转义(escape)。否则生成的函数代码会出错。如果我们运行上面的代码,我们会在控制台里面看见如下的内容:


var r=[];
r.push("<p>Hello, my name is ");
r.push("this.name");
r.push(". I&#39;m ");
r.push("this.profile.age");
return r.join("");

  等等,貌似不太对啊,this.name和this.profile.age不应该有引号啊,再来改改。


var add = function(line, js) {
  js? code += &#39;r.push(&#39; + line + &#39;);\n&#39; :
    code += &#39;r.push("&#39; + line.replace(/"/g, &#39;\\"&#39;) + &#39;");\n&#39;;
}
while(match = re.exec(tpl)) {
  add(tpl.slice(cursor, match.index));
  add(match[1], true); // <-- say that this is actually valid js
  cursor = match.index + match[0].length;
}

  占位符的内容和一个布尔值一起作为参数传给add函数,用作区分。这样就能生成我们想要的函数体了。


var r=[];
r.push("<p>Hello, my name is ");
r.push(this.name);
r.push(". I&#39;m ");
r.push(this.profile.age);
return r.join("");

  剩下来要做的就是创建函数并且执行它。因此,在模板引擎的最后,把原本返回模板字符串的语句替换成如下的内容:

return new Function(code.replace(/[\r\t\n]/g, &#39;&#39;)).apply(data);

  我们甚至不需要显式地传参数给这个函数。我们使用apply方法来调用它。它会自动设定函数执行的上下文。这就是为什么我们能在函数里面使用this.name。这里this指向data对象。

  模板引擎接近完成了,不过还有一点,我们需要支持更多复杂的语句,比如条件判断和循环。我们接着上面的例子继续写。


var template = 
&#39;My skills:&#39; + 
&#39;<%for(var index in this.skills) {%>&#39; + 
&#39;<a href="#"><%this.skills[index]%></a>&#39; +
&#39;<%}%>&#39;;
console.log(TemplateEngine(template, {
  skills: ["js", "html", "css"]
}));

  这里会产生一个异常,Uncaught SyntaxError: Unexpected token for。如果我们调试一下,把code变量打印出来,我们就能发现问题所在。


var r=[];
r.push("My skills:");
r.push(for(var index in this.skills) {);
r.push("<a href=\"\">");
r.push(this.skills[index]);
r.push("</a>");
r.push(});
r.push("");
return r.join("");

  带有for循环的那一行不应该被直接放到数组里面,而是应该作为脚本的一部分直接运行。所以我们在把内容添加到code变量之前还要多做一个判断。


var re = /<%([^%>]+)?%>/g,
  reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g,
  code = &#39;var r=[];\n&#39;,
  cursor = 0;
var add = function(line, js) {
  js? code += line.match(reExp) ? line + &#39;\n&#39; : &#39;r.push(&#39; + line + &#39;);\n&#39; :
    code += &#39;r.push("&#39; + line.replace(/"/g, &#39;\\"&#39;) + &#39;");\n&#39;;
}

  这里我们新增加了一个正则表达式。它会判断代码中是否包含if、for、else等等关键字。如果有的话就直接添加到脚本代码中去,否则就添加到数组中去。运行结果如下:


var r=[];
r.push("My skills:");
for(var index in this.skills) {
r.push("<a href=\"#\">");
r.push(this.skills[index]);
r.push("</a>");
}
r.push("");
return r.join("");

  当然,编译出来的结果也是对的。

My skills:<a href="#">js</a><a href="#">html</a><a href="#">css</a>

  最后一个改进可以使我们的模板引擎更为强大。我们可以直接在模板中使用复杂逻辑,例如:


var template = 
&#39;My skills:&#39; + 
&#39;<%if(this.showSkills) {%>&#39; +
  &#39;<%for(var index in this.skills) {%>&#39; + 
  &#39;<a href="#"><%this.skills[index]%></a>&#39; +
  &#39;<%}%>&#39; +
&#39;<%} else {%>&#39; +
  &#39;<p>none</p>&#39; +
&#39;<%}%>&#39;;
console.log(TemplateEngine(template, {
  skills: ["js", "html", "css"],
  showSkills: true
}));

  除了上面说的改进,我还对代码本身做了些优化,最终版本如下:


var TemplateEngine = function(html, options) {
  var re = /<%([^%>]+)?%>/g, reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = &#39;var r=[];\n&#39;, cursor = 0;
  var add = function(line, js) {
    js? (code += line.match(reExp) ? line + &#39;\n&#39; : &#39;r.push(&#39; + line + &#39;);\n&#39;) :
      (code += line != &#39;&#39; ? &#39;r.push("&#39; + line.replace(/"/g, &#39;\\"&#39;) + &#39;");\n&#39; : &#39;&#39;);
    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 += &#39;return r.join("");&#39;;
  return new Function(code.replace(/[\r\t\n]/g, &#39;&#39;)).apply(options);
}

以上是如何使用javascript写模板引擎代码思路和实例详解的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
JavaScript在行动中:现实世界中的示例和项目JavaScript在行动中:现实世界中的示例和项目Apr 19, 2025 am 12:13 AM

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。

了解JavaScript引擎:实施详细信息了解JavaScript引擎:实施详细信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎内部工作原理对开发者重要,因为它能帮助编写更高效的代码并理解性能瓶颈和优化策略。1)引擎的工作流程包括解析、编译和执行三个阶段;2)执行过程中,引擎会进行动态优化,如内联缓存和隐藏类;3)最佳实践包括避免全局变量、优化循环、使用const和let,以及避免过度使用闭包。

Python vs. JavaScript:学习曲线和易用性Python vs. JavaScript:学习曲线和易用性Apr 16, 2025 am 12:12 AM

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

Python vs. JavaScript:社区,图书馆和资源Python vs. JavaScript:社区,图书馆和资源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社区、库和资源方面的对比各有优劣。1)Python社区友好,适合初学者,但前端开发资源不如JavaScript丰富。2)Python在数据科学和机器学习库方面强大,JavaScript则在前端开发库和框架上更胜一筹。3)两者的学习资源都丰富,但Python适合从官方文档开始,JavaScript则以MDNWebDocs为佳。选择应基于项目需求和个人兴趣。

从C/C到JavaScript:所有工作方式从C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

JavaScript引擎:比较实施JavaScript引擎:比较实施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

超越浏览器:现实世界中的JavaScript超越浏览器:现实世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在现实世界中的应用包括服务器端编程、移动应用开发和物联网控制:1.通过Node.js实现服务器端编程,适用于高并发请求处理。2.通过ReactNative进行移动应用开发,支持跨平台部署。3.通过Johnny-Five库用于物联网设备控制,适用于硬件交互。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热工具

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。