EJS 模板速查表 - EJS 模板引擎常用语法大全
这套速查给用 Node/Express 做服务端渲染、在每页都用 EJS 的开发者。内容覆盖标签语法(<% code %>、<%= 转义输出 %>、<%- 原始 HTML %>、<%- include %>、注释)、如何对传入的 locals 做条件与循环、把页面拆进 include、共用布局,以及从 Express 里调用 res.render。与泛泛的 EJS 教程不同,条目按模板每个部分实际做什么来分,并特别标注何时会转义、何时是原始输出。读完应能搭起一个 Express 页面、用循环遍历数据、用 <%= 转义用户内容,并靠 include 复用页头页脚。
基础语法 5
<% code %><%= value %><%- value %><%# comment %><%_ code %>条件语句 3
<% if (condition) { %>...<% } %><% if (condition) { %>...<% } else { %>...<% } %><% if (a) { %>...<% } else if (b) { %>...<% } else { %>...<% } %>循环语句 4
<% for (let i = 0; i < items.length; i++) { %>...<% } %><% items.forEach(item => { %>...<% }) %><% for (const item of items) { %>...<% } %><% for (const key in obj) { %>...<% } %>包含模板 4
<%- include("header") %><%- include("footer") %><%- include("sidebar", { user }) %><%- include("partials/nav") %>布局与继承 3
<%- body %><% layout("layout") -%><%- include("header", {title: "Home"}) %>常用技巧 5
<%= user?.name || "Guest" %><%= JSON.stringify(data) %><%= new Date().toLocaleDateString() %><%= items.join(", ") %><%= Math.round(price * 100) / 100 %>Express 集成 4
app.set("view engine", "ejs")res.render("index", { title: "Home" })app.set("views", "./views")res.render("users/list", { users })渲染 API 与配置 7
ejs.render(str, data)ejs.renderFile("tpl.ejs", data, (err, html) => {})ejs.compile(str, { client: true })ejs.render(str, data, { cache: true })ejs.delimiter = "$"<%= locals.user %>const fn = ejs.compile(str); fn(data)命令示例
一个用 <%= %> 输出用户数据的循环
<% items.forEach(item => { %>
<%= item.title %>
<% }) %>item.title 走 <%= %>,其中的尖括号会被转义,用户输入无法注入标签。外层 forEach 用 <% %> 只执行不输出,循环体首尾的换行会进 HTML,需要紧凑输出时改用 <%_ %> 吃掉空白。
转义与原始输出的差别
<%- include("header", {title: "Home"}) %>
<%= user?.name || "Guest" %>
<%- post.html %>第一行用 <%- %> 是因为 include 的返回是已渲染好的 HTML;用户名用 <%= %> 保证转义;post.html 是你自己存的内容才用 <%- %>。三者混用是最容易出 XSS 的地方。
条件分支的三种写法与空白控制
<% if (a) { %>...<% } else if (b) { %>...<% } else { %>...<% } %>
<%_ code %>
<%= user?.name || "Guest" %>多分支条件的标准写法以「...」省略中间内容;换成 <%_ %> 变体后,控制标签周围的空白会被吃掉,输出里不再多出空行。条件只做展示判断、决定用哪个数据源,不要在模板里做业务判断。
在 Express 里从子目录渲染并传数据
app.set("view engine", "ejs")
app.set("views", "./views")
res.render("users/list", { users })views 目录定在根,res.render 的路径是相对它的子目录。传入的 users 在模板里用 <%= locals.users %> 取,等价于直接写 users。改 views 目录后不要忘记同步部署时的静态资源路径。
常见坑与注意事项
- <%- %> 不做任何转义,任何来自用户的值写进去都等于开放 XSS;新增字段时默认用 <%= %>,确认是自有 HTML 才降级。
- include 是同步加载:被包含文件不存在或路径写错会在渲染时抛错,而不是静默返回空,注意相对路径以当前模板所在目录为基准。
- ejs.delimiter = "$" 是全局设置,会影响之后所有模板渲染;只对部分模板用自定义分隔符时确认没有污染其它视图。
- 模板里写注释用 <%# %>,它完全不输出;想留痕迹用 HTML 注释,但 HTML 注释会随源码下发给客户端,等于公开内容。
- 生产开启 view cache 后改模板不会立刻生效,热更新失效先确认缓存配置,而不是反复重启进程。
提示
- <%= %> 会自动转义 HTML,防止 XSS 攻击,<%- %> 不转义,用于输出原始 HTML。
- include 是同步加载,会将被包含的模板内容插入到当前位置。
- EJS 支持完整的 JavaScript 语法,可以在 <% %> 中写任何 JS 代码。
- 布局继承需要 express-ejs-layouts 中间件,或者手动 include header/footer。
- 缓存模板可以提高性能:app.set("view cache", true)。
- 开发时关闭缓存:app.set("view cache", false),修改模板后立即生效。
- ejs.compile 编译出的函数可在浏览器端使用,适合客户端渲染场景。
常见问题
<%= %> 和 <%- %> 到底差在哪?为什么不能全都用 <%- %>?
差在是否做 HTML 转义。<%= %> 会把变量里的 <、>、& 等字符转成实体再输出,因此用户提交的内容不会被当成标签解析;<%- %> 直接原样插入 HTML。这正是 XSS 的分界线:文章正文、评论这类你自己审查过的 HTML 才用 <%- %>,而用户名、标题、搜索关键词等任何来自用户输入的值都必须走 <%= %>。用错的后果不是显示异常,而是页面上出现了你从未写过的脚本。
include 传的参数,在被包含的模板里怎么拿?
include 的第二个参数就是被包含模板的局部变量表。写 <%- include("sidebar", { user }) %> 时,sidebar.ejs 里可以直接用 user 访问它,也可以像访问 locals 一样写 <%= locals.user %>。被包含的模板同样能得到外层渲染时的所有变量,所以传参是「补充」而不是「替换」——不传的变量依然从父级作用域可见。这点在覆盖同名变量时容易误解,想彻底用新值覆盖就直接传同名键。
渲染出来的 HTML 里有多余空行和缩进,怎么去掉?
用 <%_ %> 标签。它在执行代码的同时吃掉标签前后的空白,专门用来压缩控制结构产生的视觉噪音。布局类模板尤其需要它,因为 <%- include("header") %> 每个换行都会原样进 HTML,累积后就是一大片无意义空白。用法一致:<%_ if (cond) { _%> … <%_ } _%>,把 if/else 和循环两端的空白都吃掉。注意它去掉的是空白而不是缩进语义,复杂排版仍要靠 CSS 处理。
开发时改了模板却没生效,是缓存的问题吗?
通常是。生产环境开启渲染缓存后,模板字符串只会被解析一次,之后都复用编译结果。开发时把 view cache 关掉,模板改动立即生效;上线后再打开以换取性能。用 ejs.render 直接渲染字符串时同理,靠 { cache: true } 控制。判断依据很直接:如果同一份数据在测试环境是新的、在生产是旧的,几乎可以确定是缓存而不是逻辑问题。
布局与 include 两种复用方式怎么选?
按复用粒度选。include 适合拼接片段:页头、页脚、侧栏这种各自独立的小块,用 <%- include("header") %> 组合即可,页面自己掌握完整结构。布局适合整页外壳:一个模板定义 html 骨架与 <%- body %> 占位,子模板只填内容,通过 <% layout("layout") -%> 指定;这需要 express-ejs-layouts 中间件支持。只用 include 也能实现布局,但要手动在每个页面首尾插一遍头尾,页面一多就容易漏。
由 巧匠 维护
公开更新于 2026年7月21日,内容持续校对官方文档。