你知道可以用20行代码实现一个JS模板引擎吗?

前言:

相信很多的老铁多多少少知道Javascript的模板引擎但是实际用过的伙伴可能没多少。在文章开始之前我先简单的介绍一下:JavaScript的模板引擎是一种用于生成动态HTML内容的工具, 其实说白了就是在运行时,模板引擎将动态地替换模板中的占位符,生成最终的HTML输出。

那么废话不多说,我们直接开整!

要实现的效果

开始之前我们得知道最终实现的效果是什么样的,如下图

js 复制代码
let TemplateEngine = function(tpl, data) {
    // magic here ...
}
let template = 'Hello, my name is <%name%>. I am <%age%> years old this year;'
console.log(TemplateEngine(template, {
    name: "Tom",
    age: 19
}));

// 输入:Hello, my name is Tom. I am 19 years old this year

获取内容

js 复制代码
var re = /<%([^%>]+)?%>/g;
let match = re.exec(tpl);

// 输入:
//   [
//     "<%name%>",
//     " name ",
//     index: 21,
//     input:
//     "Hello, my name is <%name%>. I am <%age%> years old this year;"
//   ]

看不懂上面的正则没关系直接上图Tips: 这个小玩意可以保存看一下研究正则的时候用的到哦

我们需要吧<% 内容 %> 获取到,同时采用exec的方法; 然后使用while循环进行全部匹配的遍历

exec 和**match**方法的区别就是:

  • match()方法适合用于直接得到整个匹配结果数组或获取第一个匹配结果,而不需要连续搜索。
  • exec()方法适合用于需要连续搜索并获取多个匹配结果的情况,并且可以通过更新lastIndex属性来实现连续搜索
js 复制代码
let TemplateEngine = function (tpl, data) {
    let re = /<%([^%>]+)?%>/g,
      match;
    while ((match = re.exec(tpl))) {
      console.log(match);
    }
  };

输出如下:

得到上面的结果,我们想到最简单的方法是什么?替换 replace

js 复制代码
let TemplateEngine = function (tpl, data) {
    let re = /<%([^%>]+)?%>/g,
      match;
    while ((match = re.exec(tpl))) {
      tpl = tpl.replace(match[0], data[match[1]]);
    }
    return tpl;
};

let result = TemplateEngine(template, {
    name: "Tom",
    age: 19
})
console.log(result);

// 输入:
// Hello, my name is Tom. I am 19 years old this year;

但是问题也会随时产生、如果数据变成这样呢?

js 复制代码
console.log(TemplateEngine(template, {
    name: "Tom",
    age: {
        muns:19
    }
}));

好像不行,我们需要更好的方式!

Function 构造函数

还记得 Function 吗?是的!就是构造函数!它可以使用字符串的方式运行我们的JavaScript代码!比如这样:

js 复制代码
const sum = new Function('a', 'b', 'return a + b');

console.log(sum(2, 6));
// Expected output: 8

// or 跟下方的代码同理

const sum = function (a, b) {
return a + b;
};
console.log(sum(2, 6));

我们可以把循环的语句变成这样同理对象也是,然后用数组进行收集、然后通过join变成字符串

js 复制代码
let r=[];
r.push("Hello, my name is ");
r.push( name );
r.push(". I am ");
r.push( profile.age );
r.push(" years old this year;");
return r.join("");

初始版

那么我们初步的版本应该实现成这样:

js 复制代码
const TemplateEngine = function (tpl, data) {
    let re = /<%(.+?)%>/g;
    let code = "let r=[];\n";
    let cursor = 0;
    let match = null;

    const add = function (line, js) {
      if (js) {
        code += "r.push(" + line + ");\n";
      } else {
        code += 'r.push("' + line.replace(/"/g, '\"') + '");\n';
      }
    };

    while ((match = re.exec(tpl))) {
      let trs = tpl.slice(cursor, match.index);
      add(trs);
      add(match[1], true); // 重要的
      cursor = match.index + match[0].length;
    }
    add(tpl.substr(cursor, tpl.length - cursor));
    code += `return r.join("");`;

    console.log(code);
    return tpl;
};

let template = "Hello, my name is <% name %>. I am <% profile.age %> years old this year;";

const res = TemplateEngine(template, {
    name: "Krasimir Tsonev",
    profile: { age: 29 },
});

 console.log(res);
 
 // 打印如下:
 
// let r=[];
// r.push("Hello, my name is ");
// r.push( name );
// r.push(". I am ");
// r.push( profile.age );
// r.push(" years old this year;");
// return r.join("");

得到了上面的字符串为基础,我们要做的就是执行它!

修复版

关键字:with

js 复制代码
const TemplateEngine = function (tpl, data) {
    let re = /<%(.+?)%>/g;
    let code = "with(obj) { let r=[];\n";
    let cursor = 0;
    let match = null;
    let result = null;

    const add = function (line, js) {
      if (js) {
        code += "r.push(" + line + ");\n";
      } else {
        code += 'r.push("' + line.replace(/"/g, '\"') + '");\n';
      }
    };

    while ((match = re.exec(tpl))) {
      let m = tpl.slice(cursor, match.index);
      add(m);
      add(match[1], true);
      cursor = match.index + match[0].length;
    }
    add(tpl.substr(cursor, tpl.length - cursor));
    code += `return r.join(""); }`;
    code = code.replace(/[\r\t\n]/g, " ");
    
    // 最终需要执行的
    result = new Function("obj", code).apply(data, [data]);

    return result;
  };

  let template =
    "Hello, my name is <% name %>. I am <% profile.age %> years old this year;";

  const res = TemplateEngine(template, {
    name: "Krasimir Tsonev",
    profile: { age: 29 },
  });

  console.log(res);

到现在我们已经可以对一些对象数据进行赋值,让我们在添加一些边界的情况支持if/else 和循环

js 复制代码
 const TemplateEngine = function (tpl, data) {
    let re = /<%(.+?)%>/g;
    let code = "with(obj) { let r=[];\n";
    let reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g;
    let cursor = 0;
    let match = null;
    let result = null;

    const add = function (line, js) {
      if (js) {
         code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n' 
      } else {
        code += 'r.push("' + line.replace(/"/g, '\"') + '");\n';
      }
    };

    while ((match = re.exec(tpl))) {
      let m = tpl.slice(cursor, match.index);
      add(m);
      add(match[1], true);
      cursor = match.index + match[0].length;
    }
    add(tpl.substr(cursor, tpl.length - cursor));
    code += `return r.join(""); }`;
    code = code.replace(/[\r\t\n]/g, " ");

    result = new Function("obj", code).apply(data, [data]);

    return result;
  };

let template = 
  `<div>
    <h1><% title %></h1>
    <ul>
        <% for(var i = 0; i < items.length; i++) { %><li><% items[i] %></li><% } %>
    </ul>
  </div>`;

let res = TemplateEngine(template, {
    title:"测试",
    items: ["项目1", "项目2", "项目3"],
}); 
document.body.innerHTML = res;

效果如下:

上面的代码似乎有点子繁琐啊、让我们在修订一个版本试试~

最终版本:

js 复制代码
const TemplateEngine = function(html, options) {
    var re = /<%(.+?)%>/g, 
            reExp = /(^( )?(var|if|for|else|switch|case|break|{|}|;))(.*)?/g, 
            code = 'with(obj) { var r=[];\n', 
            cursor = 0, 
            result,
                match;
    var add = function(line, js) {
            js? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
                    (code += line != '' ? 'r.push("' + line.replace(/"/g, '\"') + '");\n' : '');
            return add;
    }
    while(match = re.exec(html)) {
            add(html.slice(cursor, match.index))(match[1], true);
            cursor = match.index + match[0].length;
    }
    add(html.substr(cursor, html.length - cursor));
    code = (code + 'return r.join(""); }').replace(/[\r\t\n]/g, ' ');
    try { result = new Function('obj', code).apply(options, [options]); }
    catch(err) { console.error("'" + err.message + "'", " in \n\nCode:\n", code, "\n"); }
    return result;
}

源码地址

最后

上面的代码也是我在逛github上偶然看到觉得不错,特此记录一下!如果感兴趣大家可以关注这个项目看看~,

当然如果能帮助到大家的那么十分荣幸~ 喜欢的老铁们不用拘束了,可以点赞了~

相关推荐
victory04311 小时前
【无标题】
github
百万蹄蹄向前冲1 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5812 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路2 小时前
GeoTools 读取影像元数据
前端
ssshooter2 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友2 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry3 小时前
Jetpack Compose 中的状态
前端
dae bal4 小时前
关于RSA和AES加密
前端·vue.js
柳杉4 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog4 小时前
低端设备加载webp ANR
前端·算法