EJS[1]-源码解析
EJS[1]-源码解析
官方文档中有提到两个,最基本的使用也确实只有那两个,但是实际上可以调用的函数有五个。 本篇会介绍下这五个API的作用&本人对于该API实现的一些想法。
EJS
v1.x,代码篇幅上可以称得上短小精悍,算上注释不过400行。
建议先看完第一篇再看本文,如何使用EJS。
parse
我们会从最里边的parse
函数说起。parse
函数是根据EJS
模版来生成一段可执行的脚本字符串。
parse
、compile
、render
三个函数的参数是属于透传的,第一个参数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
函数在后边还会处理一个EJS
v1.x版本有的Filters
特性,因为不常用,而且v2.x版本已经移除了,所以就不再赘述。
compile
compile
函数中会调用parse
函数,获取脚本字符串。
并将字符串作为一个函数的主体来创建新的函数。
如果开启了debug
,compile
会添加一些额外的信息在脚本中。一些类似于堆栈监听之类的。
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
的,但是为了避免renderFile
的path
和缓存的key
重复,所以renderFile
中有这么一个小操作。
var key = path + ':string';
小记
EJS
v1.x源码非常清晰易懂,很适合作为研究模版引擎类的入门。
v2.x使用了一些面向对象的程序设计。。篇幅更是达到了接近900行(费解-.-不知道意义何在)。。有机会尝试着会去读一些v2.x版本的代码。
TODO
接下来会做一下几个模版引擎的横向对比,关于性能方面、开发难易程度、功能的完善上,各种balabala…
- Python的解码和编码
- 原生Ajax总结
- 多个知名化妆品牌现假官网,域名保护该受重视
- JavaScript操作Cookie
- Windows下Go环境安装
- Angular定义服务-Learn By Doing
- JS魔法堂:不完全国际化&本地化手册 之 理論篇
- mysql替换某个字段中的某个字符
- Angular企业级开发(1)-AngularJS简介
- PHP在Apache下500错误
- mysql 学习笔记
- MongoDB学习系列(2)--使用PHP访问MongoDB
- (cljs/run-at (JSVM. :browser) "命名空间就这么简单")
- Angular企业级开发(10)-Smart Table插件开发
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 使用Apache ab进行http性能测试
- Android Imageloader的配置的实现代码
- Linux下如何查看版本信息的方法步骤
- Android开发实现Files文件读取解析功能示例
- Android实现iPhone晃动撤销输入功能 Android仿微信摇一摇功能
- 猿实战21——商品发布之商品数据存储
- Android编程自定义进度条颜色的方法详解
- Android TextView对齐的两种方法
- Android ScrollView实现反弹效果的实例
- Ubuntu 18.04上安装 phpMyAdmin的详细教程
- Android Popupwindow弹出窗口的简单使用方法
- 解决CentOS7虚拟机无法上网并设置CentOS7虚拟机使用静态IP上网
- Android编程实现自定义Dialog的大小自动控制方法示例
- Linux中如何查看文件的创建时间详解
- Android 图片添加水印的实现方法