基本概念
ejs的基本概念十分简单,一个html = 模板 + 数据,和传统的php字符串模板拼接非常相似.
例如我们有一个列表该列表展示一组新闻,理想中的状态如下:
<article>
<h2>新闻1</h2>
<h2>新闻2</h2>
<h2>新闻3</h2>
<h2>新闻4</h2>
<h2>新闻5</h2>
</article>
如果数据是异步获取的,我们可以利用for循环拼接字符串来实现内部的多个h2
然后再追加到html中.
let data = [],result = '';
for (i=0,len = data.length;i<len;i++){
result += '<h1>'+data[i]+'</h1>>';
}
但是ejs更像是如下的样子(伪代码):
/**
* @param {string} template 字符串模板
* @param {object} data 模板需要的数据
*/
function ejs(template,data) {
return template(data);
}
也就是说ejs需要一个字符串模板,和一个data也就是我们的数据源.
template是字符串模板基于html语法但是ejs提供了特殊符号用于控制我们传入的数据如何填充到html中.
安装
这里就不提及了你可以去官网下载也可以使用npm进行安装.
我使用的版本是2.6.1
是目前的最新版本,网上的很多文章实际上有些已经过时了,因为API有些变动.
ejs中文文档中有较新的API一览.
使用
ejs分为两个版本一个是CommonJs版本,另外一个是AMD规范的版本.
后面的测试基本都是在浏览器中运行,我没有使用requireJs
直接使用script引入的ejs:
<script type="text/javascript" src="./node_modules/ejs/ejs.js"></script>
ejs会将自身挂载到window对象上,所以你只要console.log(ejs)
控制台有输出就说明安装成功了.
渲染单个数据
ejs.render('<h1><%= data %></h1>',{data:123});
返回:
<h1>123</h1>
render
方法只需要两个参数,和之前说的一样第一个为模板字符串,而二个为数据源.
ejs使用html作为模板的基础语言,所以你不需要进行学习任何额外的语法,需要了解的就是ejs给我们提供的几个模板语法.
那么<%= %>
目前看来就是输出数据,ejs所做的仅仅是用123
来替换掉<%= data %>
而已.
渲染多个数据
在ejs提供的语法中可以直接使用javascript所以你可以非常容易理解使用复杂逻辑如何完成渲染的,我们来看一个具体的例子:
const template = `
<article>
<% news.forEach(item=>{ %>
<h2><%= item %></h2>
<% }) %>
</article>
`;
const data = {
news:['新闻1','新闻2','新闻3']
};
const htmlString = ejs.render(template,data);
返回的内容:
<article>
<h2>新闻1</h2>
<h2>新闻2</h2>
<h2>新闻3</h2>
</article>
实际上去除掉ejs提供了模板语法,内容就是这个样子的:
news.forEach(item=>{
'<h2>'+item+'</h2>'
})
也就是说<% %>
的符号不会产生实际的输出,但是可以放置javascript代码用于控制中间代码块的执行流程.
有条件的渲染数据
const template = `
<article>
<% data.forEach(item=>{ %>
<% if(item.sexCode){ %>
<h2><%= item.name+'的性别是女' %></h2>
<% } else { %>
<h2><%= item.name+'的性别是男' %></h2>
<% } %>
<% }) %>
</article>
`;
const data = {
data:[
{
name:'小明',
sexCode:0
}
,
{
name:'小红',
sexCode:1
}
]
};
const htmlString = ejs.render(template,data);
输出:
<article>
<h2>小明的性别是男</h2>
<h2>小红的性别是女</h2>
</article>
这个例子稍显复杂,但是他告诉我们几点有价值的内容:
-
<% %>
用于存放javascript代码片段是确定无疑的事情了,甚至在这个例子中我们还执行了嵌套操作. -
<%= %>
实际上把内容当作表达式执行后再输出的,至少它有执行表达式的能力
本质
-
<% %>
用于执行其中的javascript代码 -
<%= %>
会对其中的javascript代码html转译
其他的模板语法
这里我就将官网的一览贴了过来然后每个提供一个例子:
- <% ‘脚本’ 标签,用于流程控制,无输出。
- <%_ 删除其前面的空格符
- <%= 输出数据到模板(输出是转义 HTML 标签)
- <%- 输出非转义的数据到模板
- <%# 注释标签,不执行、不输出内容
- <%% 输出字符串 ‘<%’
- %> 一般结束标签
- -%> 删除紧随其后的换行符
- _%> 将结束标签后面的空格符删除
先看一下2,8,9这三个.
之前我的几个例子中包含了很多空格和换行原因是因为使用的是默认的输出格式,例如我们之前使用这个例子:
<article>
<h2>小明的性别是男</h2>
<h2>小红的性别是女</h2>
</article>
来看一下修改后的效果.
使用删除空格符号:
<article>
<%_ data.forEach(item=>{ _%>
<%_ if(item.sexCode){ _%>
<h2><%= item.name+'的性别是女' _%></h2>
<%_ } else { %>
<h2><%= item.name+'的性别是男' _%></h2>
<%_ } _%>
<%_ }) _%>
</article>
输出的内容:
<article>
<h2>小明的性别是男</h2>
<h2>小红的性别是女</h2>
</article>
可以看到确实少了一些内容,但是实际上空隙是由换行符号引起的,这次我们来删除尾部的换行符号:
<article>
<% data.forEach(item=>{ -%>
<% if(item.sexCode){ -%>
<h2><%= item.name+'的性别是女' -%></h2>
<% } else { -%>
<h2><%= item.name+'的性别是男' -%></h2>
<% } -%>
<% }) -%>
</article>
输出的内容:
<article>
<h2>小明的性别是男</h2>
<h2>小红的性别是女</h2>
</article>
虽然行数减少了不过格式乱掉了.
注意:如果不想保留换行和空格可以把方法的选项参数中的rmWhitespace
设置为true
.
<%- %>的使用
默认情况下在<%= %>
之间的html字符串会被做转译处理:
<%= '<h1>hello world</h1>' %>
返回:
<h1>hello world</h1>
使用<%- %>:
该模板语法不会转译html字符串
<%- '<h1>hello world</h1>' %>
返回:
<h1>hello world</h1>
<%# %>的使用
该标签不会在最终的结果中出现,作为模板中的注释:
<%# '<h1>hello world</h1>' %>
返回的结果:
<%% %>的使用.
该操作返回的是模板字符串(string):
<%% if(id){ %>
<%% } %>
返回的结果:
<% if(id){ %>
<% } %>
API
ejs渲染的API主要有三个分别是:
- compile(str, options)
- render(str, data, options)
- renderFile(filename, data, options,callback(err,str)) 在浏览器端无效
区别是:
- render 传入模板字符串和数据返回结果
- compile 返回一个模板函数,对这个函数传入数据获取结果
- rednerFile 从文件中获取模板
ejs内部是有缓存系统的它会将模板字符串解析为一个数据结构后续的操作将不会在此解析模板.
compile就是返回了内部已经处理好模板字符串的函数,在有大量数据重复使用这套模板的时候就会带来性能提升,而对于render
和renderFile
来说需要在选项中设置cache
为true
,对于render
方法还需要指定filename
选项.
选项参数
这里也是照搬官网的:
- cache 缓存编译后的函数,需要提供 filename
- filename 被 cache 参数用做键值,同时也用于 include 语句
- context 函数执行时的上下文环境
- compileDebug 当为 false 时不编译调试语句
- client 返回独立的编译后的函数
- delimiter 放在角括号中的字符,用于标记标签的开与闭
- debug 将生成的函数体输出
- _with 是否使用 with() {} 结构。如果为 false,所有局部数据将存储在 locals 对象上。
- localsName 如果不使用 with ,localsName 将作为存储局部变量的对象的名称。默认名称是 locals
- rmWhitespace 删除所有可安全删除的空白字符,包括开始与结尾处的空格。对于所有标签来说,它提供了一个更安全版本的 -%> (在一行的中间并不会剔除标签后面的换行符)。
- escape 为 <%= 结构设置对应的转义(escape)函数。它被用于输出结果以及在生成的客户端函数中通过 .toString() 输出。(默认转义 XML)。
- root
include
使用绝对路径引入时会以此为根路径. - outputFunctionName 指定一个函数名称(例如
echo
和print
)用于在模板语法内打印输出内容. - async 当为
true
时ejs内部的渲染都将为异步,内部使用(await/async).
测试缓存对于性能的影响,使用的模板和数据如下:
const template = `
<article>
<% data.forEach(item=>{ %>
<% if(item.sexCode){ %>
<h2><%= item.name+'的性别是女' %></h2>
<% } else { %>
<h2><%= item.name+'的性别是男' %></h2>
<% } %>
<% }) %>
</article>
`;
const data = {
data: [
{
name: '小明',
sexCode: 0
}
,
{
name: '小红',
sexCode: 1
}
]
};
我使用render连续跑1000次使用的时间为480ms
.
使用compile
进行测试:
const demo = ejs.compile(template);
console.time('start');
let i = 0, len = 1000;
while (i < len) {
demo(data);
i++;
}
console.timeEnd('start');
最后的结果为15ms
,可以看到性能提升是非常明显的.
测试outputFunctionName
选项.
const data = {
content:'hello world'
};
const result = ejs.render(`<h1><% echo(content) %></h1>`,data,{
outputFunctionName:'echo'
});
console.log(result)
输出:
<h1>hello world</h1>
测试async
选项.
const data = {
content:'hello world'
};
const result = ejs.render(`<h1><%= content %></h1>`,data,{
async:true
});
result.then((result)=>{
console.log(result);
})
输出:
<h1>hello world</h1>
浏览器端使用默认缓存
网上的各种文章中都提到了ejs拥有内部缓存,给render
方法提供选项中包含filename
和cache
属性就可以以filename
进行命名然后挂载在ejs内部的缓存对象上.
但是各个文章描述的及其模糊,我查看render
和compile
的源码后也没有发现显式使用缓存的选项存在,不过依然找到了一个可以使用缓存后的函数的方法.
// 不要这次的结果,使用缓存
ejs.render(template,data,{
filename: 'test',
cache:true
});
// 获取缓存后的函数
const cacheFun = ejs.cache.get('test');
console.time('start');
let i = 0, len = 1000;
while (i < len) {
cacheFun(data);
i++;
}
console.timeEnd('start');
最后使用的时间为15ms
和使用compile
的速度一致.
个人猜测内部缓存功能是为了服务端渲染使用的或者为了配合express
使用,没有向浏览器端提供很好的支持.
注意:ejs.clearCache()
方法可以清空内部的缓存.
高级操作
包含其他模板
在一般的开发中我们经常将网页切割为多个部分,例如最简单的切割就是将页面分为导航栏
`主体区域`页尾
三个部分.
在使用的时候经常变化的是主体区域,而页尾不经常变化,但是很多页面需要同样的页尾,这个时候我们就可以将页尾单独拿出来做成一个模板.
ejs中可以使用include
命令来插入其他的模板.(注意只能在服务端中操作)
新建两个文件:
header.ejs:
<h2>
<%= content %>
</h2>
footer.ejs:
<h2>
<%= content %>
</h2>
我们编写以下代码:
const ejs = require('ejs');
const template =
`
<%- include('./header',{content:'我是页头'}) %>
<article>
我是内容区域
</article>
<%- include('./footer',{content:'我是页脚'}) %>
`;
console.log(ejs.render(template,{
filename:'whatthefuck' // 这个操作中需要一个名称,和路径无关
}));
输出:
<h2>
我是页头
</h2>
<article>
我是内容区域
</article>
<h2>
我是页脚
</h2>
有其他需要的朋友可以去查看ejs的github或者中文官网,唯一的额外信息也就这里有了.
参考
https://blog.csdn.net/xjl2713…
https://www.jianshu.com/p/81e…
https://blog.csdn.net/zhangxi…
https://www.cnblogs.com/yedey…