EJS 模板速查表 - EJS 模板引擎常用语法大全

这套速查给用 Node/Express 做服务端渲染、在每页都用 EJS 的开发者。内容覆盖标签语法(<% code %>、<%= 转义输出 %>、<%- 原始 HTML %>、<%- include %>、注释)、如何对传入的 locals 做条件与循环、把页面拆进 include、共用布局,以及从 Express 里调用 res.render。与泛泛的 EJS 教程不同,条目按模板每个部分实际做什么来分,并特别标注何时会转义、何时是原始输出。读完应能搭起一个 Express 页面、用循环遍历数据、用 <%= 转义用户内容,并靠 include 复用页头页脚。

编程语言·共 35 条命令·最后更新 2026-07-21
ejs模板nodejs

基础语法 5

<% code %>
执行 JavaScript 代码(不输出)
<%= value %>
输出变量(HTML 转义)
<%- value %>
输出变量(不转义,原始 HTML)
<%# comment %>
注释(不输出到模板)
<%_ code %>
执行代码并移除前后空白

条件语句 3

<% if (condition) { %>...<% } %>
if 条件判断
<% if (condition) { %>...<% } else { %>...<% } %>
if-else 条件判断
<% if (a) { %>...<% } else if (b) { %>...<% } else { %>...<% } %>
多条件判断

循环语句 4

<% for (let i = 0; i < items.length; i++) { %>...<% } %>
for 循环
<% items.forEach(item => { %>...<% }) %>
forEach 循环
<% for (const item of items) { %>...<% } %>
for...of 循环
<% for (const key in obj) { %>...<% } %>
for...in 循环

包含模板 4

<%- include("header") %>
包含 header.ejs 文件
<%- include("footer") %>
包含 footer.ejs 文件
<%- include("sidebar", { user }) %>
包含并传递参数
<%- include("partials/nav") %>
包含子目录中的模板

布局与继承 3

<%- body %>
在布局中渲染子模板内容
<% layout("layout") -%>
指定布局文件(需 express-ejs-layouts)
<%- include("header", {title: "Home"}) %>
传递参数到包含的模板

常用技巧 5

<%= user?.name || "Guest" %>
可选链和默认值
<%= JSON.stringify(data) %>
输出 JSON 字符串
<%= new Date().toLocaleDateString() %>
格式化日期
<%= items.join(", ") %>
数组转字符串
<%= Math.round(price * 100) / 100 %>
保留两位小数

Express 集成 4

app.set("view engine", "ejs")
设置 EJS 为模板引擎
res.render("index", { title: "Home" })
渲染模板并传递数据
app.set("views", "./views")
设置模板目录
res.render("users/list", { users })
渲染子目录模板

渲染 API 与配置 7

ejs.render(str, data)
渲染模板字符串为 HTML
ejs.renderFile("tpl.ejs", data, (err, html) => {})
异步渲染文件
ejs.compile(str, { client: true })
编译为客户端可用函数
ejs.render(str, data, { cache: true })
开启缓存渲染(生产环境)
ejs.delimiter = "$"
设置自定义分隔符为 $
<%= locals.user %>
访问 locals 传入的变量
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日,内容持续校对官方文档。

联系我们

命令或描述有误?提交反馈、商务合作或产品建议都可发送邮件给我们。

联系我们