前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# ejs模板引擎

30 秒速记

  • <%= %> 转义输出(默认用它)、<%- %> 原样输出(只给可信 HTML)、<% %> 写 JS 逻辑
  • 复用片段:<%- include('header', { title }) %>,EJS 3 删掉了旧的 <% include header %> 写法
  • 传给模板的数据要白名单,别把 ctx.query 整个塞进去
  • 模板只管展示,不在里面查库、做权限判断
  • 转义只解决 HTML 内容里的 XSS,href="javascript:..."、内联 <script> 里的数据还要单独处理

EJS 就是「在 HTML 里写 JS」:用 <% %> 写逻辑,用 <%= %> 输出变量。 最该记住的是两种输出标签的区别:<%= %> 会转义,用户内容默认都用它;<%- %> 原样输出,只用来输出 include 进来的片段或者你自己生成的可信 HTML。公共的头部、底部用 include 复用,注意 EJS 3 只支持函数写法。另外转义不是万能的,用户输入的链接放进 href 时,要校验协议,防 javascript: 这种。

理解「ejs模板引擎」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

EJS 的标签不多,一张表就能记全:

标签 作用
<% code %> 执行 JS,不输出
<%= value %> 输出并做 HTML 转义
<%- value %> 原样输出,不转义
<%# comment %> 注释,不出现在结果里
-%> / <%_ _%> 去掉标签前后的换行或空白

一个典型的列表页模板:

<%- include('partials/header', { title }) %>
<ul>
  <% if (list.length === 0) { %>
    <li>暂无数据</li>
  <% } %>
  <% list.forEach(item => { %>
    <li><%= item.name %></li>   <!-- 用户内容,转义输出 -->
  <% }) %>
</ul>
<%- include('partials/footer') %>
@前端进阶之旅: 代码已经复制到剪贴板

可以直接在 Node 里试转义效果:

const ejs = require('ejs')
console.log(ejs.render('<p><%= s %></p>', { s: '<b>hi</b>' }))
// <p>&lt;b&gt;hi&lt;/b&gt;</p>
console.log(ejs.render('<p><%- s %></p>', { s: '<b>hi</b>' }))
// <p><b>hi</b></p>
@前端进阶之旅: 代码已经复制到剪贴板

工程上两条底线:传给 render 的数据只放明确需要的字段,EJS 低于 3.1.7 的版本存在通过模板参数远程执行代码的漏洞,老项目要升级;<%- %> 只用于 include 和可信内容,代码评审时看到它输出用户数据就要拦。

面试官追问

追问 1评论区用 <%- comment %> 输出,为了保留用户的换行和链接,有什么问题?
参考回答

这就是存储型 XSS,用户提交一段 <script> 所有访客都会执行。要保留格式就先转义,再自己把换行替换成 <br>;需要富文本就用 DOMPurify 这类库做白名单过滤后再原样输出。

追问 2<%= %> 已经转义了,<a href="<%= url %>"> 是不是就安全了?
参考回答

不安全,javascript:alert(1) 里没有需要转义的字符,点了照样执行。链接要校验只允许 http: / https: 开头。

追问 3升级到 EJS 3 后,<% include header %> 报错了,怎么改?
参考回答

EJS 3 删掉了这种指令写法,改成 <%- include('header') %>。要传数据就写 <%- include('header', { title }) %>,注意用 <%-,用 <%= 会把片段 HTML 转义成文字。

追问 4模板里能直接 require 数据库查用户列表吗?
参考回答

技术上能写 JS,但不该这么干。模板里查库会让渲染变慢、错误难追、逻辑没法测。数据在中间件里查好,模板只负责把它摆出来。

# ejs模板引擎

# 具体查看ejs官方文档

30 秒速记

  • 官方仓库 github.com/mde/ejs,语法、选项、CLI 都以它的 README 为准
  • 常用选项:cache(缓存编译结果)、delimiter(换掉 %)、root / views(include 的查找目录)
  • 网上很多教程还是 EJS 2 的 include 写法,EJS 3 只支持 include() 函数
  • 版本至少 3.1.10,老版本有远程代码执行和原型污染相关的安全问题

EJS 的细节直接看官方仓库 mde/ejs 的 README 就够了,文档不长,比二手教程靠谱。 重点看三块:标签语法、render / renderFile 的选项、include 的路径规则。二手教程最容易过时的地方是 include,很多还在用 EJS 2 的 <% include xxx %>,EJS 3 已经删了。另外 EJS 修过好几个安全漏洞,3.1.7 修了模板参数导致的远程代码执行,之后的版本又补了原型污染相关的问题,老项目我会先看一眼版本号。

https://github.com/mde/ejs

理解「具体查看ejs官方文档」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

EJS 官方 README 里最常用的几个 API:

const ejs = require('ejs')

// 1. 渲染字符串模板
ejs.render('<h1><%= title %></h1>', { title: 'hi' })
// '<h1>hi</h1>'

// 2. 先编译再多次渲染,适合同一模板反复用
const tpl = ejs.compile('<li><%= name %></li>')
tpl({ name: 'a' }) // '<li>a</li>'
tpl({ name: 'b' }) // '<li>b</li>'

// 3. 渲染文件,不传回调时返回 Promise
const html = await ejs.renderFile('./view/index.ejs', { title: 'hi' }, {
  cache: process.env.NODE_ENV === 'production', // 生产缓存编译结果
  rmWhitespace: false
})
@前端进阶之旅: 代码已经复制到剪贴板

render 的第三个参数是选项,第二个参数是数据,这两个千万别混。早期版本里数据对象里的某些字段会被当成选项读取,这正是 3.1.7 之前远程代码执行漏洞的根源:攻击者通过查询参数注入 outputFunctionName 这类选项,把任意代码拼进编译后的函数。所以无论用哪个版本,都只往数据参数里放你明确列出的字段。

查文档时还要留意 include 的两种路径:相对路径相对当前模板文件,绝对路径相对 root / views 选项。用 ejs.render 渲染字符串时没有「当前文件」,要么传 filename,要么用绝对路径配 views。

面试官追问

追问 1想在 Node 脚本里直接渲染一个模板文件生成静态 HTML,用哪个 API?
参考回答

ejs.renderFile('view/index.ejs', data),不传回调时返回 Promise,await 拿到字符串后写文件就行。

追问 2模板里的 <% 和另一个模板语法冲突了,能换分隔符吗?
参考回答

能,传 delimiter: '?' 之后标签就变成 <?= ?>。EJS 3 还能用 openDelimiter / closeDelimiter 把尖括号也换掉。

追问 3include('header') 报找不到文件,查找路径是怎么算的?
参考回答

相对路径按当前模板文件所在目录找;以 / 开头的绝对路径,会去 root 或 views 选项指定的目录找。渲染字符串模板时没有当前文件,要传 filename 选项,否则相对 include 用不了。

追问 4npm audit 报 ejs 有严重漏洞,项目里没用户传模板,能不升吗?
参考回答

别赌。那个漏洞的触发条件是把用户可控对象传进 render 的数据参数,很多项目图省事传了 ctx.query 或 req.body 都中招。升到 3.1.10 以上是小版本升级,基本没有兼容问题。

# 可运行示例:区分转义输出与可信 HTML

30 秒速记

  • <%= %> 会做 HTML 转义,<%- %> 原样输出 → 用户内容一律用 <%=
  • <%- 只留给自己能担保的内容:模板
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

ejs模板引擎|Nodejs专题系列

# ejs模板引擎

30 秒速记

  • <%= %> 转义输出(默认用它)、<%- %> 原样输出(只给可信 HTML)、<% %> 写 JS 逻辑
  • 复用片段:<%- include('header', { title }) %>,EJS 3 删掉了旧的 <% include header %> 写法
  • 传给模板的数据要白名单,别把 ctx.query 整个塞进去
  • 模板只管展示,不在里面查库、做权限判断
  • 转义只解决 HTML 内容里的 XSS,href="javascript:..."、内联 <script> 里的数据还要单独处理

EJS 就是「在 HTML 里写 JS」:用 <% %> 写逻辑,用 <%= %> 输出变量。 最该记住的是两种输出标签的区别:<%= %> 会转义,用户内容默认都用它;<%- %> 原样输出,只用来输出 include 进来的片段或者你自己生成的可信 HTML。公共的头部、底部用 include 复用,注意 EJS 3 只支持函数写法。另外转义不是万能的,用户输入的链接放进 href 时,要校验协议,防 javascript: 这种。

理解「ejs模板引擎」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

EJS 的标签不多,一张表就能记全:

标签 作用
<% code %> 执行 JS,不输出
<%= value %> 输出并做 HTML 转义
<%- value %> 原样输出,不转义
<%# comment %> 注释,不出现在结果里
-%> / <%_ _%> 去掉标签前后的换行或空白

一个典型的列表页模板:

<%- include('partials/header', { title }) %>
<ul>
  <% if (list.length === 0) { %>
    <li>暂无数据</li>
  <% } %>
  <% list.forEach(item => { %>
    <li><%= item.name %></li>   <!-- 用户内容,转义输出 -->
  <% }) %>
</ul>
<%- include('partials/footer') %>
@前端进阶之旅: 代码已经复制到剪贴板

可以直接在 Node 里试转义效果:

const ejs = require('ejs')
console.log(ejs.render('<p><%= s %></p>', { s: '<b>hi</b>' }))
// <p>&lt;b&gt;hi&lt;/b&gt;</p>
console.log(ejs.render('<p><%- s %></p>', { s: '<b>hi</b>' }))
// <p><b>hi</b></p>
@前端进阶之旅: 代码已经复制到剪贴板

工程上两条底线:传给 render 的数据只放明确需要的字段,EJS 低于 3.1.7 的版本存在通过模板参数远程执行代码的漏洞,老项目要升级;<%- %> 只用于 include 和可信内容,代码评审时看到它输出用户数据就要拦。

面试官追问

追问 1评论区用 <%- comment %> 输出,为了保留用户的换行和链接,有什么问题?
参考回答

这就是存储型 XSS,用户提交一段 <script> 所有访客都会执行。要保留格式就先转义,再自己把换行替换成 <br>;需要富文本就用 DOMPurify 这类库做白名单过滤后再原样输出。

追问 2<%= %> 已经转义了,<a href="<%= url %>"> 是不是就安全了?
参考回答

不安全,javascript:alert(1) 里没有需要转义的字符,点了照样执行。链接要校验只允许 http: / https: 开头。

追问 3升级到 EJS 3 后,<% include header %> 报错了,怎么改?
参考回答

EJS 3 删掉了这种指令写法,改成 <%- include('header') %>。要传数据就写 <%- include('header', { title }) %>,注意用 <%-,用 <%= 会把片段 HTML 转义成文字。

追问 4模板里能直接 require 数据库查用户列表吗?
参考回答

技术上能写 JS,但不该这么干。模板里查库会让渲染变慢、错误难追、逻辑没法测。数据在中间件里查好,模板只负责把它摆出来。

# ejs模板引擎

# 具体查看ejs官方文档

30 秒速记

  • 官方仓库 github.com/mde/ejs,语法、选项、CLI 都以它的 README 为准
  • 常用选项:cache(缓存编译结果)、delimiter(换掉 %)、root / views(include 的查找目录)
  • 网上很多教程还是 EJS 2 的 include 写法,EJS 3 只支持 include() 函数
  • 版本至少 3.1.10,老版本有远程代码执行和原型污染相关的安全问题

EJS 的细节直接看官方仓库 mde/ejs 的 README 就够了,文档不长,比二手教程靠谱。 重点看三块:标签语法、render / renderFile 的选项、include 的路径规则。二手教程最容易过时的地方是 include,很多还在用 EJS 2 的 <% include xxx %>,EJS 3 已经删了。另外 EJS 修过好几个安全漏洞,3.1.7 修了模板参数导致的远程代码执行,之后的版本又补了原型污染相关的问题,老项目我会先看一眼版本号。

https://github.com/mde/ejs

理解「具体查看ejs官方文档」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

EJS 官方 README 里最常用的几个 API:

const ejs = require('ejs')

// 1. 渲染字符串模板
ejs.render('<h1><%= title %></h1>', { title: 'hi' })
// '<h1>hi</h1>'

// 2. 先编译再多次渲染,适合同一模板反复用
const tpl = ejs.compile('<li><%= name %></li>')
tpl({ name: 'a' }) // '<li>a</li>'
tpl({ name: 'b' }) // '<li>b</li>'

// 3. 渲染文件,不传回调时返回 Promise
const html = await ejs.renderFile('./view/index.ejs', { title: 'hi' }, {
  cache: process.env.NODE_ENV === 'production', // 生产缓存编译结果
  rmWhitespace: false
})
@前端进阶之旅: 代码已经复制到剪贴板

render 的第三个参数是选项,第二个参数是数据,这两个千万别混。早期版本里数据对象里的某些字段会被当成选项读取,这正是 3.1.7 之前远程代码执行漏洞的根源:攻击者通过查询参数注入 outputFunctionName 这类选项,把任意代码拼进编译后的函数。所以无论用哪个版本,都只往数据参数里放你明确列出的字段。

查文档时还要留意 include 的两种路径:相对路径相对当前模板文件,绝对路径相对 root / views 选项。用 ejs.render 渲染字符串时没有「当前文件」,要么传 filename,要么用绝对路径配 views。

面试官追问

追问 1想在 Node 脚本里直接渲染一个模板文件生成静态 HTML,用哪个 API?
参考回答

ejs.renderFile('view/index.ejs', data),不传回调时返回 Promise,await 拿到字符串后写文件就行。

追问 2模板里的 <% 和另一个模板语法冲突了,能换分隔符吗?
参考回答

能,传 delimiter: '?' 之后标签就变成 <?= ?>。EJS 3 还能用 openDelimiter / closeDelimiter 把尖括号也换掉。

追问 3include('header') 报找不到文件,查找路径是怎么算的?
参考回答

相对路径按当前模板文件所在目录找;以 / 开头的绝对路径,会去 root 或 views 选项指定的目录找。渲染字符串模板时没有当前文件,要传 filename 选项,否则相对 include 用不了。

追问 4npm audit 报 ejs 有严重漏洞,项目里没用户传模板,能不升吗?
参考回答

别赌。那个漏洞的触发条件是把用户可控对象传进 render 的数据参数,很多项目图省事传了 ctx.query 或 req.body 都中招。升到 3.1.10 以上是小版本升级,基本没有兼容问题。

# 可运行示例:区分转义输出与可信 HTML

30 秒速记

  • <%= %> 会做 HTML 转义,<%- %> 原样输出 → 用户内容一律用 <%=
  • <%- 只留给自己能担保的内容:模板 include 的结果、服务端安全序列化过的 JSON
  • JSON 塞进页面要再转义 < > &,否则昵称里一个 </script> 就能把脚本标签提前关掉
  • U+2028 / U+2029 在老引擎的 JS 字符串里算换行,嵌进可执行 <script> 时一起转掉
  • 读取端用 JSON.parse(el.textContent),别用 eval / innerHTML

EJS 里 <%= 会转义、<%- 不转义,用户输入默认只走 <%=。 打个比方,<%= 像是把内容装进透明袋子再贴到页面上,浏览器只能看不能执行;<%- 是直接把内容倒进 HTML。首屏数据要塞进 <script> 时只能用 <%-,这时转义的活就得由服务端序列化函数来做:把 < 换成 \u003c,>、&、U+2028、U+2029 同理。这样哪怕昵称写的是 </script><script>alert(1)</script>,在 HTML 解析器眼里也只是普通字符,JSON.parse 读出来又能还原成原样。

<h1><%= user.displayName %></h1>
<!-- <%= 会 HTML 转义;不要把用户输入放进 <%- -->
<script type="application/json" id="boot-data"><%- safeSerializedJson %></script>
@前端进阶之旅: 代码已经复制到剪贴板

safeSerializedJson 必须由服务端安全序列化器生成,并至少转义 <、>、& 和 U+2028/U+2029;不能直接对用户对象调用 JSON.stringify 后原样嵌入脚本。

← 6.1 koa2加载模板引擎7.1 busboy模块 →

fe