模板引擎介绍
先简单科普一下模板引擎是什么,模板引擎就是将数据(data)和模板(template)合并然后生成 HTML 文本。
html = 模板引擎(模板 + 数据)
在 Node.js 里,常见的模板引擎有:ejs、handlerbars、jad 等等,相信不少人都用过。今天主要是让大家了解模板引擎的核心原理,并使用 ejs 的语法来实现一个模板引擎,整个代码实现只有 20 行。
话不多说,先上代码。
// 模板引擎代码
function render(template, data) {
template = `
with(data){
let str = '';
str += `${template}`;
return str;
}
`
template = template.replace(/{%=(.+)%}/g, (...args) => {
return '${' + args[1] + '}'
})
template = template.replace(/<%(.+?)%>/g, (...args) => {
return ``;${args[1]}; str+=``
})
const res = new Function('data', template)(data)
return res;
}
使用
// 模板字符串
// 正常情况是使用 fs.readFile() 来读取文件拿到模板字符串
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- <% 这里是 js 语句 %> -->
<!-- <%= 这里是 js 表达式 %> -->
<% arr.forEach(item => { %>
<p>{%= item %}</p>
<% }) %>
<%if(Array.isArray(arr)){%>
console.log(1)
<%}%>
</body>
</html>`
console.log(render(html, {arr: [1, 2, 3]}))
输出结果为
'<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p>1</p>
<p>2</p>
<p>3</p>
1,2,3
</body>
</html>'
原理解析
我们可以看到,模板字符串内有 js 语句,如果想得到完整的结果,必须要执行这些 js 语句,但是现在拿到的只有字符串,很明显不能执行,那如何让字符串执行呢?
答案是:new Function(str)
,str 就是一个字符串,代表函数体,例如:
const fn = new Function(`
let a = 'hello '
let b = 'world'
return a + b
`)
console.log(fn()) // 输出 hello world
现在有办法让字符串执行 js 语句了,但是语法不对,因为如果以现在的模板直接 new Function()
的话,相当于创建了一个如下的函数:
function tpl() {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- <% 这里是 js 语句 %> -->
<!-- <%= 这里是 js 表达式 %> -->
<% arr.forEach(item => { %>
<p>{%= item %}</p>
<% }) %>
<%if(Array.isArray(arr)){%>
console.log(1)
<%}%>
</body>
</html>
}
很明显语法错误,所以现在要把模板解析为可执行的 js 语句。
可以尝试把上面的模板字符串转换成如下可执行的字符串:
'let str = '';
str += `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>`;
arr.forEach(item => {
str += `<p>${ item }</p>`;
});
if (Array.isArray(arr)) {
str += `${arr}`;
}
str += `
</body>
</html>
`;
return str;'
为了方便大家阅读,转换后的字符串做了美化。
看到这不知道大家有没有思路了?本质就是字符串替换,只有找到一定的规律就可以完成这件事。
其实只需三步即可:
- 字符串内声明 str 变量,拼接并用 str 拼接模板字符串,并 return str。
template = `
let str = "";
str += `${template}`;
return str;`
拼接完成后模板变成这样:
'let str = '';
str += `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<% arr.forEach(item => { %>
<p>{%= item %}</p>
<% }) %>
<%if(Array.isArray(arr)){%>
<p>{%= item %}</p>
<%}%>
</body>
</html>`;
return str;'
- 将
{%= %}
替换成${}
代码如下:
template = template.replace(/{%=(.+)%}/g, (...args) => {
return '${' + args[1] + '}'
})
- 让 js 语句可执行,所以要删掉
<% %>
,并在语句内部去拼接str
,这里拼接的时候一定要使用``
,因为内部解析表达式的时候用的是${}
。
实现代码:
template = template.replace(/<%(.+?)%>/g, (...args) => {
return ``;${args[1]}; str+=``
})
其实到这,这个模板引擎就已经可以工作了,但是还有一个地方需要注意,就是我们在模板内部使用变量时,外层的对象默认被解构了:
`<% arr.forEach(item => { %>
<p>{%= item %}</p>
<% }) %>
<%if(Array.isArray(arr)){%>
<p>{%= item %}</p>
<%}%>`
我们传入模板的数据是{arr: [1, 2, 3]}
,理论上应该用 data.arr
去访问 arr
,但实际在使用的时候直接用的是 arr
,这里是怎么实现的呢?
答案是 ```with`` 语句,看一下 demo 演示:
const data = { arr: [1, 2, 3] }
with (data) {
arr.forEach(item => console.log(item));
}
所以我们要在字符串最外层在加一个 with
:
template = `
with(data){
let str = '';
str += `${template}`;
return str;
}
`
现在变量访问的问题也解决了就,然后就是生成一个这样的函数 const fn = new Fuction('data', template)
调用 fn 得到解析好的模板字符串 fn(data)
模板引擎大功告成。
小结
思路就是字符串替换,关键是大家拼接的时候一定要多打印,多实验,哪里缺少补哪里。明白思路之后就是一个查缺补漏的体力活了。
最后
如果这篇文章对你有帮助的话,希望得到你的认可~~~