EJS[1]-源码解析

官方文档中有提到两个,最基本的使用也确实只有那两个,但是实际上可以调用的函数有五个。 本篇会介绍下这五个API的作用&本人对于该API实现的一些想法。

EJSv1.x,代码篇幅上可以称得上短小精悍,算上注释不过400行。

parse

我们会从最里边的parse函数说起。parse函数是根据EJS模版来生成一段可执行的脚本字符串。

parsecompilerender三个函数的参数是属于透传的,第一个参数str为模版源字符串,第二个参数options是可选的配置参数。

parse函数在拿到str以后,会将字符串拆成一个个的字符来匹配。

抛开匹配到界定符的逻辑外,其余的一些匹配都是自增+1形式的,比如\n\\\'或任意的普通文本。 也就是说,如果一个EJS模版文件没有用到太多的动态脚本,强烈建议开启cache。 就如同下图的代码,EJS会循环字符串的所有字符,执行一遍拼接,这个工作后续是有大量的重复的,如果开启了cache后,就可以避免这个问题,这也是可以提升性能的。

ejs.render('<h1>Title</h1>')

其次就是判断字符命中为界定符: 会进一步的去查找结束的界定符,如果没有找到则会抛出异常。

var open = options.open || exports.open || '<%'
var close = options.close || exports.close || '%>'
for (var i = 0, len = str.length; i < len; ++i) {
  var stri = str[i];

  // 判断是否匹配为开始界定符
  if (str.slice(i, open.length + i) == open) {

    // ... some code

    var end = str.indexOf(close, i);

    // 如果没有找到结束的界定符,抛出异常
    if (end < 0){
      throw new Error('Could not find matching close tag "' + close + '".');
    }
  }
}

在得到了JavaScript脚本的范围(在字符串中的下标)后,我们就可以开始着手拼接脚本的工作了。 首先我们需要判断这一段脚本的类型,因为我们知道EJS提供了有三种脚本标签<% code %><%- code %><%= code %>

三种处理方式也是不一样的,第一个会直接执行脚本,其余两个会输出脚本执行的返回值。 所以三种标签的差异就体现在这里: 这里是将要包裹脚本的前缀后缀给创建了出来。 最终的返回结果会是 prefix + js + postfix。 我们会发现prefix里边有一个line变量,这里用到了逗号运算符/逗号操作符,很巧妙。 作为一个行号的输出,既不会影响程序的执行,又可以在出错的时候帮助我们快速定位问题所在。

  switch (str[i]) {
    case '=': // 序列化返回值
      prefix = "', escape((" + line + ', ';
      postfix = ")), '";
      ++i;
      break;
    case '-': // 直接返回
      prefix = "', (" + line + ', ';
      postfix = "), '";
      ++i;
      break;
    default: // 仅仅是执行
      prefix = "');" + line + ';';
      postfix = "; buf.push('";
  }

三种标签拼接后的示例:

//                                       var buf = []

ejs.render('<h1><%= "Title" %></h1>') // buf.push('<h1>', escape((1, 'Title')), '</h1>')

ejs.render('<h1><%- "Title" %></h1>') // buf.push('<h1>', (1, 'Title'), '</h1>')

ejs.render('<h1><% "Title" %></h1>')  // buf.push('<h1>'); 1; 'Title'; buf.push('</h1>')

//                                       return buf.join('')

P.S. parse函数在后边还会处理一个EJSv1.x版本有的Filters特性,因为不常用,而且v2.x版本已经移除了,所以就不再赘述。

compile

compile函数中会调用parse函数,获取脚本字符串。 并将字符串作为一个函数的主体来创建新的函数。 如果开启了debugcompile会添加一些额外的信息在脚本中。一些类似于堆栈监听之类的。

str = exports.parse(str, options) // 获取脚本字符串
var fn = new Function('locals, filters, escape, rethrow', str) // 创建函数

return function (locals) {
  fn.call(this, locals, filters, escape, rethrow);
}

render

render函数会调用compile函数,并执行它得到模版处理后的结果。 cache的判断也是在render函数这里做的。 我们存在内存中用来缓存的模版并不是执行后的结果,而是创建好的那个函数,也就是compile的返回值,也就是说,我们缓存的其实是构建函数的那一个步骤,我们可以传入不同的变量来实现动态的渲染,并且不必多次重复构建模版函数。

renderFile

renderFile函数只能够在node环境下使用。。因为有涉及到了io的操作,需要取读取文件内容,然后调用render函数。 同时renderFile也是可以使用cache的,但是为了避免renderFilepath和缓存的key重复,所以renderFile中有这么一个小操作。

var key = path + ':string';

小记

EJSv1.x源码非常清晰易懂,很适合作为研究模版引擎类的入门。 v2.x使用了一些面向对象的程序设计。。篇幅更是达到了接近900行(费解-.-不知道意义何在)。。有机会尝试着会去读一些v2.x版本的代码。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring Boot Redis 入门(上)

    摘要: 原创出处 http://www.iocoder.cn/Spring-Boot/Redis/ 「芋道源码」欢迎转载,保留摘要,谢谢!

    芋道源码
  • SpringBoot系列之日志框架介绍及其原理简介

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    用户1208223
  • 使用ASP.NET Core 3.x 构建 RESTful API - 3.1 资源命名

    之前讲了RESTful API的统一资源接口这个约束,里面提到了资源是通过URI来进行识别的,每个资源都有自己的URI。URI里还涉及到资源的名称,而针对资源的...

    solenovex
  • vivo web service:亿万级规模web服务引擎架构

    vivo web service是开发团队围绕奇点内核打造出的基于vivo手机平台的web服务引擎,该服务引擎从浏览器产品线中经过多年迭代而出。除了提供可靠的基...

    2020labs小助手
  • 用 RSocket 解决响应式服务之间的的通讯-Part 3:基于 RSocket 进行抽象

    如果你看过本系列的前两篇文章,应该已经已经发现 RSocket 提供了一些底层的 API。可以直接使用交互模型中的方法进行操作,而且可以没有任何限制来回发送帧。...

    涤生
  • [docker](一)docker-namespaces资源隔离和cgroups资源限制

    Docker容器本质上是宿主机上的进程。Docker通过namespaces实现了资源隔离,通过cgroups实现了资源限制,通过写时复制机制(copy-on-...

    baron
  • 详解 Flink Metrics 原理与监控实战

    本文由 Apache Flink Contributor 刘彪分享,对什么是 Metrics、如何使用 Metrics 两大问题进行了详细的介绍,并对 Metr...

    zhisheng
  • pytorch 1.2 与 Tensorflow 2.0 谁优谁劣?

    Tensorflow作为长盛不衰的深度学习框架,一直广泛受到工业、科研学术界的欢迎,而近期推出Tensorflow2.0更是将Tensorflow...

    用户6719124
  • [docker](二)docker架构

    docker使用传统的cs架构,总架构图如下所示。用户通过Docker client与Docker daemon简历通信,并将请求发送给后者。

    baron
  • ROS 2 Eloquent Elusor安装和使用汇总

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    zhangrelay

扫码关注云+社区

领取腾讯云代金券