千家信息网

怎么用JavaScript写一款EJS模板引擎

发表于:2026-11-03 作者:千家信息网编辑
千家信息网最后更新 2026年11月03日,本篇内容介绍了"怎么用JavaScript写一款EJS模板引擎"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学
千家信息网最后更新 2026年11月03日怎么用JavaScript写一款EJS模板引擎

本篇内容介绍了"怎么用JavaScript写一款EJS模板引擎"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

1. 起因

部门最近的一次分享中,有人提出来要实现一个ejs模板引擎,突然发现之前似乎从来都没有考虑过这个问题,一直都是直接拿过来用的。那就动手实现一下吧。本文主要介绍ejs的简单使用,并非全部实现,其中涉及到options配置的部分直接省略了。如有不对请指出,最后欢迎点赞 + 收藏。

2. 基本语法实现

定义render函数,接收html字符串,和data参数。

const render = (ejs = '', data = {}) => {}

事例模板字符串如下:

    
<%= name %>
<%= age %>

可以使用正则将<%= name %>匹配出来,只保留name。这里借助ES6的模板字符串。将name用${}包裹起来。

props中第2个值就是匹配到的变量。直接props[1]替换。

[  '<%= name %>',  ' name ',  16,  '\n    
<%= name %>
\n
<%= age %>
\n']
const render = (ejs = '', data = {}) => {    const html = ejs.replace(/<%=(.*?)%>/g, (...props) => {        return '${' + props[1] + '}';        // return data[props[1].trim()];    });}

3. Function函数

这里得到的html是一个模板字符串。可以通过Function将字符串编程可执行的函数。当然这里也可以使用eval,随你。

    
${ name }
${ age }

Function是一个构造函数,实例化后返回一个真正的函数,构造函数的最后一个参数是函数体的字符串,前面的参数都为形式参数。比如这里传入形参name,函数体通过console.log打印一句话。

const func = new Function('name', 'console.log("我是通过Function构建的函数,我叫:" + name)');// 执行函数,传入参数func('yindong'); // 我是通过Function构建的函数,我叫:yindong

利用Function的能力可以将html模板字符串执行返回。函数字符串编写return,返回一个拼装好的模板字符串、

const getHtml = (html, data) => {    const func = new Function('data', `return \`${html}\`;`);    return func(data);    // return eval(`((data) => {  return \`${html}\`; })(data)`)}const render = (ejs = '', data = {}) => {    const html = ejs.replace(/<%=(.*?)%>/g, (...props) => {        return '${' + props[1] + '}';    });    return getHtml(html, data);}

4 with

这里render函数中props[1]的实际上是变量名称,也就是name和age,可以替换成data[props[1].trim()],不过这样写会有一些问题,偷个懒利用with代码块的特性。

with语句用于扩展一个语句的作用域链。换句人话来说就是在with语句中使用的变量都会先在with中寻找,找不到才会向上寻找。

比如这里定义一个age数字和data对象,data中包含一个name字符串。with包裹的代码块中输出的name会先在data中寻找,age在data中并不存在,则会向上寻找。当然这个特性也是一个with不推荐使用的原因,因为不确定with语句中出现的变量是否是data中。

const age = 18;const data = {    name: 'yindong'}with(data) {    console.log(name);    console.log(age);}

这里使用with改造一下getHtml函数。函数体用with包裹起来,data就是传入的参数data,这样with体中的所有使用的变量都从data中查找了。

const getHtml = (html, data) => {    const func = new Function('data', `with(data) { return \`${html}\`; }`);    return func(data);    // return eval(`((data) => { with(data) { return \`${html}\`; } })(data)`)}const render = (ejs = '', data = {}) => {    // 优化一下代码,直接用$1替代props[1];    // const html = ejs.replace(/<%=(.*?)%>/g, (...props) => {    //     return '${' + props[1] + '}';    // });    const html = ejs.replace(/<%=(.*?)%>/gi, '${$1}');    return getHtml(html, data);}

这样就可以打印出真是的html了。

    
yindong
18

5. ejs语句

这里扩展一下ejs,加上一个arr.join语句。

    
<%= name %>
<%= age %>
<%= arr.join('--') %>
const data = {    name: "yindong",    age: 18,    arr: [1, 2, 3, 4]}const html = fs.readFileSync('./html.ejs', 'utf-8');const getHtml = (html, data) => {    const func = new Function('data', ` with(data) { return \`${html}\`; }`);    return func(data);}const render = (ejs = '', data = {}) => {    const html = html = ejs.replace(/<%=(.*?)%>/gi, '${$1}');    return getHtml(html, data);}const result = render(html, data);console.log(result);

可以发现ejs也是可以正常编译的。因为模板字符串支持arr.join语法,输出:

    
yindong
18
1--2--3--4

如果ejs中包含forEach语句,就比较复杂了。此时render函数就无法正常解析。

    
<%= name %>
<%= age %>
<% arr.forEach((item) => {%>
<%= item %>
<%})%>

这里分两步来处理。仔细观察可以发现,使用变量值得方式存在=号,而语句是没有=号的。可以对ejs字符串进行第一步处理,将<%=变量替换成对应的变量,也就是原本的render函数代码不变。

const render = (ejs = '', data = {}) => {    const html = ejs.replace(/<%=(.*?)%>/gi, '${$1}');    console.log(html);}
    
${ name }
${ age }
<% arr.forEach((item) => {%>
${ item }
<%})%>

第二步比较绕一点,可以将上面的字符串处理成多个字符串拼接。简单举例,将a加上arr.forEach的结果再加上c转换为,str存储a,再拼接arr.forEach每项结果,再拼接c。这样就可以获得正确的字符串了。

// 原始字符串retrun `    a    <% arr.forEach((item) => {%>        item    <%})%>    c`// 拼接后的let str;str = `a`;arr.forEach((item) => {    str += item;});str += c;return str;


在第一步的结果上使用/<%(.*?)%>/g正则匹配出<%%>中间的内容,也就是第二步。

const render = (ejs = '', data = {}) => {    // 第一步    let html = ejs.replace(/<%=(.*?)%>/gi, '${$1}');    // 第二步    html = html.replace(/<%(.*?)%>/g, (...props) => {        return '`\r\n' + props[1] + '\r\n str += `';    });    console.log(html);}

替换后得到的字符串长成这个样子。

    
${ name }
${ age }
` arr.forEach((item) => { str += `
${ item }
`}) str += `

添加换行会更容易看一些。可以发现,第一部分是缺少首部`的字符串,第二部分是用str存储了forEach循环内容的完整js部分,并且可执行。第三部分是缺少尾部`的字符串。

    
${ name }
${ age }
`// 第二部分 arr.forEach((item) => { str += `
${ item }
`})// 第三部分 str += `

处理一下将字符串补齐,在第一部分添加let str = `,这样就是一个完整的字串了,第二部分不需要处理,会再第一部分基础上拼接上第二部分的执行结果,第三部分需要在结尾出拼接`; return str; 也就是补齐尾部的模板字符串,并且通过return返回str完整字符串。

// 第一部分let str = `    
${ name }
${ age }
`// 第二部分 arr.forEach((item) => { str += `
${ item }
`})// 第三部分 str += ``;return str;

这部分逻辑可以在getHtml函数中添加,首先在with中定义str用于存储第一部分的字符串,尾部通过return返回str字符串。

const getHtml = (html, data) => {    const func = new Function('data', ` with(data) { let str = \`${html}\`; return str; }`);    return func(data);}

这样就可以实现执行ejs语句了。

const data = {    name: "yindong",    age: 18,    arr: [1, 2, 3, 4],    html: '
html
', escape: '
escape
'}const html = fs.readFileSync('./html.ejs', 'utf-8');const getHtml = (html, data) => { const func = new Function('data', ` with(data) { var str = \`${html}\`; return str; }`); return func(data);}const render = (ejs = '', data = {}) => { // 替换所有变量 let html = ejs.replace(/<%=(.*?)%>/gi, '${$1}'); // 拼接字符串 html = html.replace(/<%(.*?)%>/g, (...props) => { return '`\r\n' + props[1] + '\r\n str += `'; }); return getHtml(html, data);}const result = render(html, data);console.log(result);

输出结果:


yindong

18

1

2

3

4

6. 标签转义

<%=会对传入的html进行转义,这里编写一个escapeHTML转义函数。

const escapeHTML = (str) => {    if (typeof str === 'string') {        return str.replace(/&/g, "&").replace(//g, ">").replace(/ /g, " ").replace(/"/g, """).replace(/'/g, "'");    } else {        return str;    }}

变量替换的时候使用escapeHTML函数处理变量。这里通过\s*去掉空格。为了避免命名冲突,这里将escapeHTML改造成自执行函数,函数参数为$1变量名。

const render = (ejs = '', data = {}) => {    // 替换转移变量    // let html = ejs.replace(/<%=\s*(.*?)\s*%>/gi, '${escapeHTML($1)}');    let html = ejs.replace(/<%=\s*(.*?)\s*%>/gi, `\${        ((str) => {            if (typeof str === 'string') {                return str.replace(/&/g, "&").replace(//g, ">").replace(/ /g, " ").replace(/"/g, """).replace(/'/g, "'");            } else {                return str;            }        })($1)    }`);    // 拼接字符串    html = html.replace(/<%(.*?)%>/g, (...props) => {        return '`\r\n' + props[1] + '\r\n str += `';    });    return getHtml(html, data);}

getHtml函数不变。

const getHtml = (html, data) => {    const func = new Function('data', `with(data) { var str = \`${html}\`; return str; }`);    return func(data);}

<%-会保留原本格式输出,只需要再加一条不使用escapeHTML函数处理的就可以了。

const render = (ejs = '', data = {}) => {    // 替换转义变量    let html = ejs.replace(/<%=\s*(.*?)\s*%>/gi, '${escapeHTML($1)}');    // 替换其余变量    html = html.replace(/<%-(.*?)%>/gi, '${$1}');    // 拼接字符串    html = html.replace(/<%(.*?)%>/g, (...props) => {        return '`\r\n' + props[1] + '\r\n str += `';    });    return getHtml(html, data, escapeHTML);}

输出样式:


yindong

18

1

2

3

4

<div>escapeHTML</div>

至此一个简单的ejs模板解释器就写完了。

"怎么用JavaScript写一款EJS模板引擎"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

字符 字符串 函数 变量 模板 语句 参数 处理 部分 输出 第一部 结果 二部 也就是 代码 内容 就是 转义 引擎 包裹 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 上海卓越科技互联网 为想互联网科技怎么样 苏州互联网科技贷款公司 金融科技和互联网金融 北京三清互联网科技 青岛中康互联网医疗科技有限公司 随着互联网科技发展 成都互联网科技公司100强 互联网科技教育视频 华服互联网科技官网 晓花互联网科技有限公司网址 互联网5g黑科技有哪些 广州市恰铎互联网科技有限公司 互联网科技如何创业 科技赋能视角下互联网保险发展 伊宁互联网科技做什么的 美国互联网科技实力 互联网金融科技元年 产业互联网科技服务公司 张召忠谈中国互联网科技 奇酷互联网络科技 安控科技rtu产品工业互联网 随着互联网科技发展 栖莱科技互联网股份有限公司 卓翼科技产业互联网合作 山东有哪些互联网科技公司 电子商务与互联网科技的关系 江苏互联网智能科技软件 福建互联网展示科技馆设计 安徽九吞吴互联网科技有限公司
0