likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 4

前言:

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

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

要实现的效果

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

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

获取内容

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: 这个小玩意可以保存看一下研究正则的时候用的到哦

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

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

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

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

输出如下:

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

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

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;

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

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

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

Function 构造函数

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

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变成字符串

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("");

初始版

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

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

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 和循环

 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;

效果如下:

你知道可以用20行代码实现一个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上偶然看到觉得不错,特此记录一下!如果感兴趣大家可以关注这个项目看看~,

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

转载自:https://juejin.cn/post/7293783088399548435
评论
请登录