ES6 开始支持模板字符串(Template literals), 支持如下的写法:
`string text ${expression} string text`;
其实在很多模板引擎中, 常常会有这样需求, 比如常用的 doT, 使用类似的语法
- <div>
- {{=1+2}}
- </div>
- // 或者支持循环或者判断 {{for(var i in it){}}}
- <span>
- {{=i}}
- </span>
- {{}}}
简单插值的实现
我们先来看看一个模板引擎基本的实现需要什么, 先不考虑循环和判断, 只支持变量运算.
打开 Babel https://www.babeljs.cn/repl , 输入
- const a = 1;
- console.log(`Hi\n${2 + 3}!dk${a}`);
经过 Babel 转义以后, 可以看到
- "use strict";
- var a = 1;
- console.log("Hi\n".concat(2 + 3, "!dk").concat(a));
可以看到, Babel 把插值提取到 concat 入参, 通过函数入参的自计算实现了 Template literals. 在我们的使用中, 其实没法直接做到这样的效果.
但是仿造 Babel 的做法, 我们可以整理一下自己的思路:
通过正则把插值和实际字符串拆开
通过 eval 或者 new Function()实现插值的计算
通过 concat 拼接, 也可以使用 String.raw
代码实现如下:
- var str = "string text ${1 + 2} string text ${2 + 3} test";
- function template(str) {
- var pattern = /\$\{.*?\}/g;
- var patternCapture = /\$\{(.*?)\}/g;
- // 将非插值字符串分割出来
- var strArr = str.split(pattern);
- // 将插值字符串分割出来
- var rawArr = str
- .match(patternCapture)
- .map(item => item.replace(patternCapture, "$1"));
- // eval 转换
- var valueArr = rawArr.map(r => eval(r));
- // 使用 reduce 和 concat 拼接,
- return strArr.reduce(
- (acc, curr, index) => acc.concat(curr, valueArr[index] || ""),
- ""
- );
- // 或者使用 String.raw
- // return String.raw({ raw: strArr }, ...valueArr);
- }
- console.log(template(str));
- new Function
上面使用 eval 对插值进行了求值, 实际上在平时使用中, eval 是不推荐的. 而且用 eval 去解析一些循环判断和条件判断也不是很方便.
所以接下来使用 new Function()去构建一个模板函数. 在这之前, 需要说明一下 new Function 的用法.
new Function ([arg1[, arg2[, ...argN]],] functionBody)
前面传入的是函数所需要的参数, 最后是函数体, 函数体是一个包括函数定义的 JavaScript 语句字符串.
其次, 根据上面的插值实现, 我们可以使用字符串拼接把插值计算之后和正常的字符串拼接起来.
对于简单插值, 使用 {{}} 包裹, 而语句使用 {{~}} 区别. 下面是一个简单实现
- function render(tem, data) {
- let template = tem;
- template = template
- .replace(/[\r\n\t]/g, "")
- .replace(/\{\{~(.+?)\}\}/g, (_, p1) => {
- return '";' + p1 + 'out+="';
- })
- .replace(/\{\{(.+?)\}\}/g, (_, p1) => {
- return '"; out+=""+'+ p1 +'+""; out+="';
- });
- template = 'var out=""; out += "'+ template +'";return out;';
- var _render = new Function(...Object.keys(data), template);
- return _render(...Object.keys(data).map(k => data[k]));
- }
- var template =
- "test array{{~for (var i in group.jobs) {}}{{group.jobs[i]}} {{~}}} test obj {{group.jobs[1]}} {{group.name}} leader 是{{leader}}";
- var data = {
- group: {
- name: "group1",
- jobs: ["job1", "job2"]
- },
- leader: "张三"
- };
- console.log(render(template, data));
在给模板注入数据时, 可以使用 with(data){}的方式, 我不喜欢使用 with, 所以把参数分解后传入了.
(完).
来源: https://www.cnblogs.com/liuyongjia/p/10962850.html