zoukankan      html  css  js  c++  java
  • 沙漠之鹰和战术匕首--两款前端极简模板引擎

    一、前言

    说起前端模板引擎,那可真是多如牛毛,只要是前端coder,怎么着你都听说或用过几款,社区里面的文章也有介绍,或者问问度娘,这里不再赘述。其中比较知名的有 artTemplatedoT、mustache等。

    本文介绍两款极简模板引擎:一款原创format引擎,一款优化template引擎。每个模板引擎都只有区区三十行左右的代码。

    二、模板原则

    模板概念的提出,不管起源是什么,最根本的原则一定是要解决开发中的问题:显示逻辑和数据逻辑能够分离。而实际开发需求中,两者分离的需求也不尽相同。

    三、实际问题

    在实际开发中,我们很多时候就是以下几种模板格式化需求:

    1、将一堆数据直接塞到模板字符串中,源数据很多的时候是对象,保不齐也需要是数组或者多参;

    2、偶尔用一下模板中的循环逻辑、判断逻辑;

    3、如果太复杂,就干脆load一段json然后配合一个函数来处理了,复杂度和可维护性往往是冲突的;

    四、战术匕首

    先看第一个需求:直接将数据塞入到字符串中。

    如果撇开前端模板不谈,你会用什么方法?很显然,在 js 中处理字符串最为得心应手的莫过于正则表达式,一个简单的 replace 函数就可以完成查询和替换的工作:

    1 var str = "<div>我是一个{name}</div>";
    2 var data = {name:"测试用例"};
    3 var ret = str.replace(/{(w+)}/g, function(match, index) {
    4     return data[index] || match;
    5 });
    6 console.log(ret)

    上面这段代码足够简单,但是思路却是非常赞,使用js内置的正则引擎来高效处理字符串替换。而这个工作其实就是“模板引擎”的本职工作。

    考虑数据源多样性、正则表达式预编译等需求,最终完善成以下本文要介绍的原创format引擎

    (function($, undefined) {
        //测试浏览器是否支持正则表达式预编译
        var baseReg = /{([w.]+)}/g, numReg = /^d+$/,
            //预编译核心的正则表达式,以提高正则匹配效率
            formatReg = baseReg.compile ? baseReg.compile(baseReg.source, "g") || baseReg : baseReg,
            //其他工具函数
            toString = Object.prototype.toString, slice = Array.prototype.slice;
        //对外接口
        $.format = function(string, source){
            if( source === undefined || source === null )return string;
            var isArray = true, type = toString.call(source),
                //检测数据源
                data = type === "[object Object]" ? (isArray = false, source) : type === "[object Array]" ? source : slice.call(arguments, 1),
                N = isArray ? data.length : 0;
            //执行替换
            return String(string).replace(formatReg, function(match, index) {
                var isNumber = numReg.test(index), n, fnPath, val;
                if( isNumber && isArray ){
                    n = parseInt(index, 10);
                    return n < N ? data[n] : match;
                }else{ //数据源为对象,则遍历逐级查找数据
                    fnPath = index.split(".");
                    val = data;
                    for(var i=0; i<fnPath.length; i++)
                        val = val[fnPath[i]];
                    return val === undefined ? match : val;
                }
            });
        };
    })(window.jQuery || window.Zepto || window);

    如果拿一个武器来比喻的话,我觉得战术匕首很适合:短小精悍,使用顺手。

    这把刀是这样用的:

    //模板内置到js中
    var tmpl = ['<div>',
        '这里是一个{name}',
        '这里是其他元素',
    '</div>'].join("");
    var ret = $.format(tmpl, data);
    
    //从页面元素中获取模板
    var tmpl = $("#tmpl")[0].innerHTML;
    var ret = $.format(tmpl, data);

    以上引擎能解决实际开发中多数的模板需求了,但是在有些时候,数据结构比较复杂,有循环、判断等逻辑在内,这个场景下,format就力不从心了。

    五、沙漠之鹰

    jQuery的作者John Resig的一个Micro-Templating非常合我胃口,不足三十行的代码,完成了上述含有逻辑处理的模板引擎,如果说format是一把前端开发中的战术匕首的话,这个无疑是一只沙漠之鹰。不过,这只沙漠之鹰用起来不太顺手:

    1、只有从页面读取的模板才能缓存,而且必须是id元素;

    2、为了节约前缀数据名,使用with这个令我觉得不爽的语法;

    3、识别标识符不能更换,因为有些模板会输出到 jsp 页面(请谅解我们项目的特殊性),<%%>跟jsp的标志混淆;

    于是,动手优化了大神的代码,经过实践完善后的代码如下(中间完善的过程不再一一叙述):

    (function($) {
        var tmplCache={}, fnCache={}, guid=0, toString = Object.prototype.toString, compile = function( tmpl, sp ){
            //默认分隔符
            var f = sp || "%",
                //动态创建函数,并增加数据源引用(data/my)
                fn = new Function("var p=[],my=this,data=my,print=function(){p.push.apply(p,arguments);};p.push('" +
                    // Convert the template into pure JavaScript
                    tmpl
                    .replace(/[
    	
    ]/g, " ")
                    .split("<" + f).join("	")
                    .replace(new RegExp("((^|" + f + ">)[^\t]*)'", "g"), "$1
    ")
                    .replace(new RegExp("\t=(.*?)" + f + ">", "g"), "',$1,'")
                    .split("	").join("');")
                    .split(f + ">").join("p.push('")
                    .split("
    ").join("\'") + "');return p.join('');");
            return fn;
        };
        //对外接口
        $.template = function(tmpl, data, sp) {
            sp = sp||"%";
            var fn = toString.call(tmpl) === "[object Function]" ? tmpl
                    : !/W/.test(tmpl) ? fnCache[tmpl+sp] = fnCache[tmpl+sp] || compile(document.getElementById(tmpl).innerHTML, sp)
                    : (function(){
                        for(var id in tmplCache)if( tmplCache[id] === tmpl ) return fnCache[id];
                        return (tmplCache[++guid] = tmpl, fnCache[guid] = compile(tmpl, sp));
                    })();
            return data ? fn.call(data) : fn;
        };
    })(window.jQuery || window.Zepto || window);

    优化后的代码仅仅保留了最为核心的正则处理部分,同时增加以下特性:

    1、任意模板均自动编译和缓存;

    2、模板可以从js输入,也可以从页面id元素中获取;

    3、支持自定义分隔符;

    4、支持数据源别名;

    因为最终模板会编译成一个函数,所以,js能干的事儿,模板中都可以干,同时语法上跟 jsp 的标志相似,有过类似开发经验的同学几乎没有任何学习成本,是的,这个引擎也仅仅三十行左右。

    经过优化改造的引擎,趁手了,强大了,用起来才有沙漠之鹰的感觉:

    这把枪的用法如下:

    //模板内置到js中
    var templ = "this is a <%=my.type%> <%=data.name%> too.";
    var ret = $.template(templ, {name:"demo", type:"easy"});
    
    //从页面id元素中获取模板
    var ret = $.template("tmpl", {name:"demo", type:"easy"});

    更多用法,请移步这里

    六、语法和性能

    模板引擎几乎都会自造模板语法,但是就开发习惯而言,当你看到以下模板的时候,不知道作为前端开发人员,你会不会糊涂:

    <ul>
        {{ for (var val, i = 0, l = it.list.length; i < l; i ++) { }}
            {{ val = it.list; }}
            <li>用户: {{=val[i].user}}/ 网站:{{=val[i].site}}</li>
        {{ } }}
    </ul>

    所以,在性能和语法习惯上,我个人非常倾向于后者。而性能问题,在日常开发中,不会变态到循环 10000 次,每次有 100 条数据的情况吧?

    这个引擎在性能上不占太多优势(IE下除外),但是极为精简的代码,可以让你感觉不到他的存在,完全可以放入全站js中,随时供您调遣和使用。

    当然,如果你的需求远不止上述两个方面,你可以尝试使用其他模板引擎,当然,还是建议优先考虑开发习惯,然后参考性能、体积等因素综合考虑,其中 doT 是除了语法习惯有点别扭之外,性能和体积综合最为优秀的一款引擎。而企鹅公司的artTemplate在支持模板调试,并可以在node环境下使用,功能还是很强大的。

    我用artTemplate的性能测试用例跑了一下,本文从这里开始到末尾均是性能测试截图。为了防止名称跟其他模板冲突,测试用例中,沙漠之鹰的名字换成了 mcTemplate。有兴趣的同学,可以到这里自己试验

     

    上图为 chrome下高频测试结果

    上图为 chrome下低频测试结果

    上图为 firefox下高频测试结果

    上图为 firefox下低频测试结果

    上图为 IE11 下高频测试结果

    上图为 IE11 下低频测试结果

    IE7 下无法执行高频测试,直接卡死。上图是IE7低频测试结果

    IE6 下无法执行高频测试,直接卡死。上图是IE6低频测试结果

  • 相关阅读:
    nginx 下 bootstrap fa 字体异常问题
    centos7 & mysql
    ssh authentication魔鬼细节--.ssh文件夹权限
    python self introspection
    __getattr__ 与动态属性
    dict.items vs six.iteritems
    django ATOMIC_REQUESTS
    HDU 4309 Seikimatsu Occult Tonneru (状压 + 网络流)
    UVaLive 4064 Magnetic Train Tracks (极角排序)
    UVa 11645 Bits (暴力+组合数学)
  • 原文地址:https://www.cnblogs.com/zjcn/p/4275816.html
Copyright © 2011-2022 走看看