JS模板引擎:基于字符串拼接

目的

编写一个基于字符串拼接的js模板引擎雏形,这里并不会提供任何模板与数据的绑定。

基本原理

Javascript中创建函数的方式有多种,包括:

1. var func = function () {...}

2. function func () {...}

3. var func = new Function (...);

其中new Function的方法给到了我们很大的自由度,比如:

var func = new Function(‘a‘, ‘b‘, ‘return a+b;‘); func(1, 2); // 输出: 3

这样我们就可以通过字符串来创建函数。

如果我们有模板:

<div>
{{data.name}}
</div>

那么我们希望它达成的作用是:

function func (data) {
   return ‘<div>‘+data.name+‘</div>‘;
}

为了让这个转换变得更简单,我们实际上将得到的是:

function func (data) {
    var arr = [];
    arr.push(‘<div>‘);
    arr.push(data.name);
    arr.push(‘</div>‘);
    return arr.join(‘‘);
}

接下来我们的目标就很明确了:

我们要利用new Function的方式,完成模板到目标函数之间的转换。

使用变量

观察目标函数与模板的区别,总结之后我们得到了如下的代码

function Templer (str) {
  return new Function (‘data‘,
    ‘var arr = []; arr.push("‘
      +str.replace(/[\r\t\n]/g, ‘ ‘) // 在代码中去掉换行符
        .replace(/{{/g, ‘");arr.push(‘) // 替换变量的开始部分
        .replace(/}}/g, ‘);arr.push("‘) // 替换变量的结尾部分
      +‘");return arr.join("");‘
    );
}

于是我们可以这样使用:

var func = Templer(‘<div>{{data.name}}</div>‘);
/* 输出:
function anonymous(data) {
var arr = []; arr.push("<div>");arr.push(data.name);arr.push("</div>");return arr.join("");
}
*/
func({name: ‘笨笨‘});
// 输出:"<div>笨笨</div>"

模板中的逻辑

虽然我们完成了最基本的变量替换,但显然功能还过于单一。接下来我们来做第二步:在模板中嵌入逻辑(js代码)。

由于我们所做的就是将模板处理成正确的js代码再使用new Function来生成函数,因此将js代码嵌入到我们的模板引擎中也显得非常轻松自然。

试着写一个模板和目标函数,比较两者的异同,得到以下的结果:

function Templer (str) {
  return new Function (‘data‘,
    ‘var arr = []; arr.push("‘
      +str.replace(/[\r\t\n]/g, ‘ ‘) // 在代码中去掉换行符
        .replace(/{{/g, ‘");arr.push(‘) // 替换变量的开始部分
        .replace(/}}/g, ‘);arr.push("‘) // 替换变量的结尾部分
        .replace(/{% (.+?) %}/g, ‘");$1arr.push("‘) // 替换js部分
      +‘");return arr.join("");‘
    );
}

测试一下:

var func = Templer(tplStr);
/* tplStr:
<div>{{data.name}}</div>
<div>家庭成员</div>
{% for (var i=0, member; member = data.members[i], member; i++) { %}
    <div>{{i+1}}-名称:{{member}}</div>
{% } %}
*/
func(({name: ‘笨笨‘, members: [‘宝宝‘,‘小宝宝‘,‘小笨笨‘,‘笨笨‘]}));

扩展

最后我们来做一些扩展,包括引入 for ... in ...的语法和include

直接上代码:

(function (g) {
    function Templer(name, tpl) {
        if (!Templer._cached[name]) { // 用name保存建立过的模板函数
            Templer._cached[name] = new Function(‘data‘, ‘var arr = []; (function (d) { arr.push("‘
                + tpl
                    .replace(lineBreakRegex, lineBreakReplace) // 替换换行符
                    .replace(jsRegex, jsReplace) // 替换js
                    .replace(variableStartRegex, variableStartReplace) // 替换变量起始
                    .replace(variableEndRegex, variableEndReplace) // 替换变量终止
                +‘");})(data); return arr.join("");‘);
        }
        return Templer._cached[name];
    }
    Templer._cached = {};
    var variableStartRegex = /{{/g,
        variableStartReplace = ‘");arr.push(‘,
        variableEndRegex = /}}/g,
        variableEndReplace = ‘);arr.push("‘,
        lineBreakRegex = /[\r\t\n]/g,
        lineBreakReplace = ‘ ‘,
        jsRegex = /{% (.+?) %}/g,
        jsReplace = function () {
            var str = arguments[1], stop = false;
            // 开始处理特殊格式
            stop || (str = str.replace(‘end for‘, function () {
                stop = true;
                return ‘}‘;
            }));
            stop || (str = str.replace(startForRegex, function () {
                stop = true;
                var a = arguments[1],
                    b = arguments[2],
                    i = nextVar();
                return ‘for (var ‘+i+‘ = 0, ‘+a+‘; ‘+a+‘ = ‘+b+‘[‘+i+‘]; ‘+i+‘++) {‘;
            }));
            stop || (str = str.replace(includeRegex, function () {
                stop = true;
                var tplName = arguments[1];
                return ‘arr.push(Templer("‘+tplName+‘")(d));‘;
            }));
            // 结束处理特殊格式
            return ‘");‘+str+‘arr.push("‘;
        },
        startForRegex = /^for (.+?) in (.+?)$/g,
        endForRegex = /^end for$/g,
        includeRegex = /^include (.+?)$/g,
        nextId = 0,
        nextVar = function () {
            return ‘_‘ + (nextId++).toString(36);
        };

    window.Templer = Templer;
})(window);
<div id="div1"></div>
<script type="text/template" id="tpl2">
    <h4>Pets:</h4>
    <ol>
    {% for pet in d.pets %}
        <li><span>{{pet.name}}</span>
        <ul>
        {% for food in pet.food %}
            {% if (food === ‘banana‘) { %}
            <li>balala~</li>
            {% } else { %}
            <li>{{food}}</li>
            {% } %}
        {% end for %}
        </ul>
        </li>
    {% end for %}
    </ol>
</script>
<script type="text/template" id="tpl1">
    <p>{{d.name}} - {{d.info.age}}</p>
    {% if (d.info.age > 20) { %}
    <div>age is 20+</div>
    {% } else { %}
    <div>age is 20-</div>
    {% } %}
    <p>
        {% include tpl2 %}
    </div>
</script>
<script type="text/javascript" src="templer.js"></script>
<script type="text/javascript">
    Templer(‘tpl2‘, document.getElementById(‘tpl2‘).innerHTML);
    document.getElementById(‘div1‘).innerHTML = Templer(‘tpl1‘, document.getElementById(‘tpl1‘).innerHTML)({
        name: ‘Miao‘,
        info: {age: 27},
        pets: [{
            name: ‘Meow‘,
            food: [‘apple‘, ‘banana‘]
        }, {
            name: ‘Doge‘,
            food: [‘apple‘, ‘banana‘]
        }]
    });
</script>

总之就是正则+替换,这里只是提供一个思路和雏形,知道了基本的做法其他的只要发挥想象就行了。

预编译

既然我们是将模板字符串处理成new Function要使用的函数体字符串,我们就可以利用node或其他工具将模板预编译成js文件中的函数,免去即时编译的耗费。

// 模板引擎的做法
new Function (‘data‘, 函数体);

// 预编译到js文件中
Template[tplName] = function (data) {
     函数体
}

总结

通过使用正则替换处理模板字符串来得到new Function的函数体,从而将模板转换为模板函数。由于只是基于字符串的处理和拼接,因此这样的引擎在web端和server端都是可用的,当然这种方式也不会提供任何数据绑定的功能。

时间: 2024-10-09 18:26:30

JS模板引擎:基于字符串拼接的相关文章

前后端数据交互处理基于原生JS模板引擎开发

json数据错误处理,把json文件数据复制到----> https://www.bejson.com/ 在线解析json 这样能直观的了解到是否是json数据写错,在控制台打断点,那里错误打那里断点,观察是否有错误. <!DOCTYPE html> <html> <head> <title>前后端数据交互处理原生JS模板引擎开发</title> <meta charset ='utf-8'> <script type=&

js模板引擎原理,附自己写的简洁模板引擎

js处理DOM交互非常普遍,但DOM结构单纯用js字符串拼接简直难以维护,不方便理解和扩展. 下面展现了js模板引擎的实现原理: html中的模板 <script id="mytpl"> <div> 我的名字是:$name$ <br/> 今年$age$了! </div> </script> 因为script不会被浏览器解析和渲染,最大限度节省了浏览器资源,textarea标签同样可以达到效果.Template标签就是这样的目的

掌握js模板引擎

最近要做一个小项目,不管是使用angularjs还是reactjs,都觉得大材小用了.其实我可能只需要引入一个jquery,但想到jquery对dom的操作,对于早已习惯了双向绑定模式的我,何尝不是一种痛苦. 听过这样一句话:"技术没有缺席,只有姗姗来迟",很多技术自己不知道,并非没有.今天我想介绍的就是一个简单的js模板引擎artTemplate,让我们扬帆起航吧- 一.概述 artTemplate 是新一代 javascript 模板引擎,它采用预编译方式让性能有了质的飞跃,并且充

为什么要使用JS模板引擎

我之前在写一个输入联想控件的时候,改过好几个版本,每个版本不是因为性能不好就是因为代码凌乱而被推翻,最后用了understore模板引擎,效果有明显改善.整好这两天在研究互联网技术架构,发现很多的开发框架前端都是使用js模板引擎,感悟真的是大道至简,殊途同归啊,哈哈. 关于为什么使用js模板引擎,在博客园发现园友的一片文章<js模版引擎handlebars.js实用教程——为什么选择Handlebars.js>,该文已经做了详细解答.下面内容转自该文: 据小菜了解,对于java开发,涉及到页面

JS模板引擎

JS模板引擎 :ArtTemplate 1.为什么需要用到模板引擎 我们在做前端开发的时候,有时候经常需要根据后端返回的json数据,然后来生成html,再显示到页面中去. 例如这样子: var data = [ {text: "测试一"}, {text: "测试二"}, {text: "测试三"}, {text: "测试四"} ]; function generateList(data) { var listHtml = &

js模板引擎--artTemplate

js模板引擎--artTemplate 以前研究过一段时间的handlebars,但因为其渲染性能略逊于腾讯的artTemplate(在artTemplate的GitHub官网上有推荐的性能测试地址),貌似最近耳边听到得最多的模板引擎也就是artTemplate了,所以就花个时间来研究下吧... artTemplate是新一代的javascript模板引擎,若采用拥有V8引擎的chrome浏览器进行测试,其渲染性能甚至能达到知名模板引擎Mustache的20倍以上以及模板引擎tmpl的40倍以上

js模板引擎介绍搜集

js模板引擎越来越多的得到应用,如今已经出现了几十种js模板引擎,国内各大互联网公司也都开发了自己的js模板引擎(淘宝的kissy template,腾讯的artTemplate,百度的baiduTemplate等),如何从这么多纷繁的模板引擎中选择一款适合自己的呢,笔者最近对主流的js模板引擎(mustache,doT,juicer,artTemplate,baiduTemplate,Handlebars,Underscore)做了一番调研,分享出来希望对大家有用. 从这几个指标来比较js模板

doT js 模板引擎【初探】

js中拼接html,总是感觉不够优雅,本着要优雅不要污,决定尝试js模板引擎. JavaScript 模板引擎 JavaScript 模板引擎作为数据与界面分离工作中最重要一环,越来越受开发者关注. 常见模板引擎 baiduTemplate(百度)\artTemplate(腾讯)\juicer(淘宝)\doT\ tmpl\ handlebars\ easyTemplate\ underscoretemplate \ mustache \kissytemplate等 为什么选择doT: doT.j

百度JS模板引擎 baiduTemplate 1.0.6 版

A.baiduTemplate 简介 0.baiduTemplate希望创造一个用户觉得“简单好用”的JS模板引擎 注:等不及可以直接点左侧导航中的”C.使用举例“,demo即刻试用. 1.应用场景: 前端使用的模板系统 或 后端Javascript环境发布页面 http://tangram.baidu.com/BaiduTemplate 2.功能概述: 提供一套模板语法,用户可以写一个模板区块,每次根据传入的数据, 生成对应数据产生的HTML片段,渲染不同的效果. 3.特性: 1.语法简单,学