js模版深度解析

js模版深度解析

 

js模版对于一个健壮的组件库来说,至关重要。犹如建筑一栋大楼,模版就是钢筋,数据就是水泥,事件就是布线和弱电。本文将从一个小函数讲起,然后重点探讨js模版的实现模式、易用性、可扩展性,然后再对ext的模版体系做简单分析。

 

由于工作原因,本人一直在维护一个datagrid组件,datagrid的需求千变万化,大概60%的需求都是对单元格的处理,刚刚开始的时候需要一个功能就加一个功能,比如单元格需要根据数据改变背景颜色,于是我便在表格生成之后直接操作dom,比如带checkbox的datagrid,翻页后需要保存已选状态,于是我便在表格生成之后查找checkbox然后再选中。需要在增加,datagrid也慢慢变的臃肿起来,不堪重负,leader也决定开始重构了。在重构之初,我便决定,在表格生成之前把需要处理的都完成,这样就可以节省查询dom的时间。这样以来,前期创建需要处理的逻辑就会很多,所以这里就需要一个很完善的模版体系来做支持,否则玩到最后又会变的很臃肿。

 

于是我尝试着写了一个简单的基于对象模式的模版,代码如下:

 

复制代码
/**
 *
 *  对象模式创建模版
 *
 *  @param {Array} attrs 生成的节点数组
 *         @param {String} type 类型
 *         @param {Array|Object} attr 属性
 *         @param {Array|Object} child  子节点
 *         @param {Number} num  子节生成个数
 *         @param {Function} func  处理函数
 *         @param {Array} data  数据
 *
 *     @param {Element|String} target
 */
var tpl = function(ats, target) {
    target = fast.id(target);
    if (fast.isArray(ats) && ats.length > 0 && target.appendChild) {
        for (var i = 0, len = ats.length; i < len; i++) {
            var attrs = ats[i], tag = attrs.tag, attr = attrs.attr || {}, data = attrs.data, func = attrs.func, child = attrs.child, num = attrs.num ? attrs.num : 1, j = 0;
            var fragment = document.createDocumentFragment();
            for (; j < num; j++) {
                var isFunc = false;
                if (data) {
                    if (child) {
                        if (fast.isArray(child)) {
                            for (var k = 0, l = child.length; k < l; k++) {
                                child[k].data = data[j];
                            }
                        } else {
                            child.data = data[j];
                        }
                    } else {
                        if (func) {
                            attr = func(j, attr, data);
                            isFunc = true;
                        } else {
                            data = fast.values(data);
                            attr.text = data[j];
                        }
                    }
                }
                (isFunc === false) && func && ( attr = func(j, attr, data));
                var nodes = fast.node(tag, attr);
                fragment.appendChild(nodes);
                child && tpl(child, nodes);
            }
            target.appendChild(fragment);
        }
    }
};
复制代码

 

另外创建了一个基类,这个基类后面的例子都会用到,希望读者注意。

View Code

 

在模版使用之前,我们需要预先定义一组数据,这组数据后面的几个模版体系都会用到

View Code

 

这个模版的使用事例如下:

var td = [{tag:"td",num:5,attr:{text:"text"}}];
var tr = [{tag:"tr",num:1000,child:td,data:data}];
var tbody = [{tag:"tbody",child:tr}];
tpl([{tag:"table",attr:{style:"width:100%",border:"1"},child:tbody}],"example");

当然,您也可以这样写:

复制代码
tpl([{tag:"table",attr:{style:"width:100%",border:"1"},
    child:[{tag:"tbody",
        child:[{tag:"tr",num:1000,
            child:[{tag:"td",num:5,attr:{text:"text"}}],
        data:data}]}
        ]}],
    "example");
复制代码

 

该模版的核心思路就是递归创建dom,支持对每个dom绑定数据,支持外部函数调用(helper),支持内嵌数据处理,支持一次创建多个平级dom。


对于一个组件库来说,感觉这样很完美了,于是我兴致冲冲的想拿其他的模版体系做对比。找了一大圈,发现别人玩模版不是这样玩的,大部分都是先拼装字符串,然后再放到一个闭包里来处理,再返回。

于是我模仿着别人写了一个原型,代码如下:

 

复制代码
/*
 *  字符串模式创建模版
 *
 */
var tp = function(str, data) {
    var str = fast.id(str) ? fast.html(str) : str, str = str.replace(/<\#(\s|\S)*?\#>/g, function(p) {
        return p.replace(/("|\\)/g, "\\$1").replace("<#", '_s.push("').replace("#>", '");').replace(/<\%([\s\S]*?)\%>/g, '",$1,"')
    }).replace(/\r|\n/g, ""), keys = [], values = [], i;
    for (i in data) {
        keys.push(i);
        values.push(data[i]);
    }
    return (new Function(keys, "var _s=[];" + str + " return _s;") ).apply(null, values).join("");
};
复制代码

调用方式大致如下:

复制代码
<div id="tptest" style="height:100px;overflow-y: auto"></div>
<script id="t1" type="text/tpl">
<#<table width="100%" border="1">#>
    for (var i = 0, l = list.length; i < l; i ++) { 
        <#<tr>#>
            for(var p in list[i]){
                <#<td>
                    <%list[i][p]%>
                </td>#>
            }
        <#</tr>#>
    }
<#</table>#>
</script>
<script>
var tpdata = {
    list: data
};
fast.html("tptest",tp("t1",tpdata));
复制代码

 

做了一下性能对比,乖乖,这个性能比对象模式更快,而且对象模式能实现的,这个基本都能实现。但是对于处理单个dom的方式上,总感觉缺点什么,想来想去,原来这种方式不能把一个dom拿出来单独玩,需要跳到模版里面去,这里就需要注意环境变量以及逻辑关系了。

 

还是不死心,于是一狠心把ext的模版抽筋剥皮拿了出来,代码如下(运行需要上面的fast基类,未经详细测试,不建议用于生产环境):

View Code

调用方式:

复制代码
<div id="exttpl" style="height:100px;overflow-y: auto"></div>
<script>
var etpl = ['<table width="100%" border=1>', '<tpl for=".">', '<tr>', 
'<td>{name}</td>', '<td>{sex}</td>', '<td>{age}</td>','<td>{date}</td>','<td>{uid}</td>', '</tr>', '</tpl>', '</table>'];
extpl.constructor(etpl);
extpl.overwrite("exttpl",data);
</script>
复制代码

 

ext模版分两种,一种是templete,数据模型只处理字符串和数组,另外一种是xtemplete,可以处理对象,上面剥离的是xtemplete。在剥离的过程不禁惊叹js的精髓,原来js可以这样写!ext的大神们很精巧的拼装了一个内置函数,核心函数在generateparse,generate负责组装,parse负责解析。

然后测试了一下,速度更是惊人,几乎和测试字符串模式(tp函数)跑平!那么多的判断分支,神了,再膜拜一下ext。

细嚼完ext,于是又回头看了一下jquery,由于时间问题没来得及剥离,粗略的写了一个用例。

复制代码
<div style="height:100px;overflow-y: auto">
    <table width="100%" border=1 id="jqtpl"></table>
</div>
<script id='templateName' type='text/x-jquery-tmpl'>
<tr><td>${name}</td><td>${sex}</td><td>${age}</td><td>${date}</td><td>${uid}</td></tr>
</script>
<script type="text/javascript"> 
$('#templateName').tmpl(data).appendTo('#jqtpl');
</script> 
复制代码

测试中,jquery的用时在最长的,因为没有剥离出内核,所以不能妄加评论。但是它的写法是最精简的,值得学习和借鉴。

 

全部测试数据如下(单位:秒):

chrome:

测试对象的模式:用时 0.04700016975402832
测试字符串模式:用时 0.03299999237060547
测试extTmplete模式:用时 0.03299999237060547
测试jquerytmpl模式:用时 0.11500000953674316
 
ie9:
 
测试对象的模式:用时 0.44099998474121093
测试字符串模式:用时 0.03399991989135742
测试extTmplete模式:用时 0.032000064849853516
测试jquerytmpl模式:用时 0.3899998664855957
 
走了一圈之后再回顾自己写的模版,发现了自己的很多不足,急于结果的实现,对过程的把控没做合理的布局,实现上还需要做进一步推敲。
 
总结:
优秀js模版几个关键因素:
 
一、支持多级数据,无论ext还是jquery都支持。比如data数据,模版内可以做data.param1循环也可以做data.param2循环。
二、支持模版助手helper,可以通过助手任意处理模版里的控件,给模版提供灵活性。
三、有完善的容错机制。
四、支持内嵌循环。
五、易用性和速度效率,jquery的模版为什么会使用那么广是有原因的,用户能不能接受才是最关键的。
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值