我的js模板引擎reMarker第一版(循环遍历算法,非正则)

2010-12-15 01:03

我的js模板引擎reMarker第一版(循环遍历算法,非正则)

by simon4545

at 2010-12-14 17:03:00

original http://www.cnblogs.com/simon4545/archive/2010/12/14/1905855.html

 首先介绍一下reMarker出现的原因:
1.公司里新做了一个公网项目,使用的是java+json+javscript+html的前后台结合的机制,后端生成json代码,前端所有的页面渲染基本使用js的模板引擎,引擎生成了html后
和Json数据集成,渲染成最后的页面。
2.因为Java开发人员都比较熟悉freemark模板的语法,为了减少学习成本,我们使用js的模板引擎用的也是freemark的语法。而我所在的公司有一个风格,几乎不用公网的代码,
所有的代码基本上从前到后都是员工自已开发的。
3.而且后来经过我的实际测试,互联网上的一些模板引擎,jTemplate/jst/sweetTemplate/ext template也的确在效率上并不能高过我所开发的这个模板引擎。
下面列出的这个算法,也并不是什么高明的东西。其创意来自于一个ooCSS的面向对象CSS处理机制。
4.我们可以看到目前公网上的js模板引擎用的都是正则来处理特征,进行选择匹配,最后动态生成function,返回替换后的字符串。而我们知道javascript/java/c#等语言用的都
是NFA(非确定型有穷自动机)正则引擎,它的优点是longest-leftmost匹配,回溯,组捕获等,但同时他相对于DFA(确定型有穷自动机)正则引擎的缺点也就出现了,速度相对慢
一点,虽然是native code,但是我并不是很信任他的效率。后来做过一过不成熟的测试,发现它的确会比一般的字符串遍历寻找要慢(事实证明,我的这个测试代码并不科学,
下次会分析)。
所以我就在这里写下了这样一个模板引擎,支持freemark语法,可以拓展?html/?html/?default等扩展方法在util对象中。
就像上面所说的,他是有bug的,但我并没有打算修复,毕竟我只是为了测试效率才写的。当然这个bug并不复杂,如果你喜欢,可以去修正一下,估计十几分钟就Ok了,谁的代
码能没有bug呢?残念。。。
经过测试,实现我的demo page中相同的功能,主流的引擎没有发现比这个更快的(除了我马上要出的下一版,呵呵).谢谢

 /*

    这个版本是有bug的,如果freemark语法不是模板的最后一段,会出现bug
    但是它是使用数组循环的方法,为了和以后的正则比较效率,会保持这个版本不删除!
*/
(
function(){

    
if (window.reMarker || window.r) {
        
throw "reMarker is exist"
    };
    window.reMarker 
= window.r = m = function(tpl, data){
        
this.tpl = tpl;
        
this.data = data;
        
this.scope = null;
        
this.vars = [];
    }
    window.r.regRuler 
= {
        ruler: 
function(str){
            
var listArr = r.util.removeEmpty(str.split(' '));
            
/*这里如果不用function而直接return回替换值的话,千次执行会快出5毫秒,和机器有关             switch(listArr[0]){
             case "list": return this.rulerList(listArr);break;
             case "if":  return 'if(' + listArr.slice(1).join('') + '){';break;
             case "break": return 'break;';break;
             case '/#list': return '}})();';break;
             case 'else': return '}else{';break;
             case "/#if": return '}';break;
             case 'elseif':return '}else if(' + listArr.slice(1).join('') + '){';break;
             case 'switch':return 'switch(' + listArr.join('').replace('switch','') + '){';break;
             case 'case': return ('case ' + listArr[1] + ':');break;
             case 'default':return 'default:';break;
             case '/#switch': return '}';
             }
             
*/
            
var ruler = {
                
"list"this.rulerList,
                
"if"this.rulerIf,
                
"break"this.rulerBreak,
                
'/#list'this.rulerEndList,
                
'else'this.rulerElse,
                
"/#if"this.rulerEndIf,
                
'elseif'this.rulerElseIf,
                
'switch'this.rulerSwitch,
                
'case'this.rulerCase,
                
'default'this.rulerDefault,
                
'/#switch'this.rulerEndSwitch
            };
            
return (ruler[listArr[0]]).call(this, listArr);
            
        },
        rulerEndSwitch:
function(arr){
            
return '}';
        },
        rulerCase:
function(arr){
            
return ('case ' + arr[1+ ':');
        },
        rulerDefault:
function(){
            
return 'default:'
        },
        rulerSwitch:
function(arr){
            
return 'switch(' + arr.join('').replace('switch',''+ '){'
        },
        rulerElseIf:
function(){
            
if(arr.length<2){return false;}
            
return '}else if(' + arr.slice(1).join(''+ '){';
        },
        rulerBreak:
function(){
            
return 'break;'
        },
        rulerElse:
function(arr){
            
return '}else{';
        },
        rulerEndIf:
function(arr){
            
return '}';
        },
        rulerIf: 
function(arr){
            
if(arr.length<2){return false;}
             
return 'if(' + arr.slice(1).join(''+ '){'
        },
        rulerEndList:
function(arr){
            
return '}})();';
        },
        rulerList:
function(arr){
            
var listName, loopName, loopIndexName, loopHasNextName, result = [];
             
if(arr.length!=4){return;}
             loopName
=arr[3];
             listName
=arr[1];
             loopIndexName 
= loopName + '_index';
             loopHasNextName 
= loopName + '_has_next';
             
             result.push(
'(function(){');
            
if (!/^\w+$/.test(listName)) {
                result.push(
'var _list=' + listName + ';');
                listName 
= '_list';
            }
            result.push([
'var _i=0''_count=' + listName + '.length', loopName, loopIndexName, loopHasNextName + ';'].join(','));
            result.push(
'for(;_i<_count;_i++){');
            result.push(loopName 
+ '=' + listName + '[_i];');
            result.push(loopIndexName 
+ '=_i;');
            result.push(loopHasNextName 
+ '=_i!==_count-1;');
            
return result.join('');
        }
    }
    window.r.util 
= {
        trim: 
function(str){
            
return this.replace(/(^\s*)|(\s*$)/g, "");
        },
        lTrim: 
function(str){
            
return this.replace(/(^\s*)/g, "");
        },
        rTrim: 
function(str){
            
return this.replace(/(\s*$)/g, "");
        },
        removeEmpty: 
function(arr){
            
return arr.join(',').replace(',,'',').split(',');
        }
    }
    m.prototype.process 
= function(data, scope){
        
return this.splitVar(this.tpl);
        
    };
    
    m.prototype.splitVar 
= function(tpl){
        
var chunks = [], idx = 0, lastIndex = 0, le = tpl.length;
        
var self = this;
        
var printPrefix = "__buf__.push(";
        
var replaced = [];
        tpl
=tpl.replace("\r\n",'').replace("\t",'');
        
function pushStr(str){
            str 
= str.replace(/'/g, "\\'");
            
if (str !== '') {
                replaced.push(printPrefix 
+ '\'' + str + '\');');
            }
        }
        
var peek = function(tok){
                
if (tok == "<") {
                    
if (tpl[idx + 1== "#" && tpl[idx + 2== "-" && tpl[idx + 2== "-") {
                        findEnd(
">");
                        chunks.push(tpl.substring(lastIndex, idx));
                        pushStr(chunks[chunks.length
-1].replace("#",'!'));
                        
return true;
                    }
                    
else 
                        
if (tpl[idx + 1== "#") {
                            findEnd(
">");
                            chunks.push(tpl.substring(lastIndex, idx));
                            replaced.push(r.regRuler.ruler(chunks[chunks.length
-1].slice(2-1)));
                            
return true;
                        }
                        
else 
                            
if (tpl[idx + 1== "/" && tpl[idx + 2== "#") {
                                findEnd(
">");
                                chunks.push(tpl.substring(lastIndex, idx));
                                replaced.push(r.regRuler.ruler(chunks[chunks.length
-1].slice(1-1)))
                                
return true;
                            }
                }
                
else if(tok=="$") {
                    
if (tpl[idx + 1== "{") {
                        findEnd(
"}");
                        chunks.push(tpl.substring(lastIndex, idx));
                        replaced.push(printPrefix 
+ chunks[chunks.length-1].slice(2-1+ ');');
                        
return true;
                    }
                }
else{
                    
            
return false;
                    
                }

        }
        
var findEnd = function(tok){
            
for (var i = idx+2 ; i < le; i++) {
                    
if (tpl[i] == tok) {
                        
//增加非语法句
                        if (tpl[lastIndex] != '<') {
                            chunks.push(tpl.substring(lastIndex, idx))
                            pushStr(chunks[chunks.length
-1]);
                        }
                        lastIndex 
= idx;
                        idx 
= i + 1;
                        
break;
                    }
            }
        }
        
        
while (idx < le) {
            
if (peek(tpl[idx])) {
                
//增加语法句
                lastIndex = idx;
            }
            
else {
                idx
++;
            }
        }
        
        
        chunks.push(tpl.substring(lastIndex));
        replaced.push(tpl.substring(lastIndex))
        
        replaced 
= ["var __buf__=[],$index=null;$util.print=function(str){__buf__.push(str);};with($data){", replaced.join(''), "} return __buf__.join('');"].join('');
        
//need 4ms when the function is null
        this.compiled = new Function("$data""$util", replaced);
        
var util = {};
        
if (m.util) {
            
var _util = m.util;
            
for (var key in _util) {
                util[key] 
= _util[key];
            }
        }
        
return this.compiled.call(this.scope || window, this.data, util);
    };
    
    m.prototype.log 
= function(){
        
if (typeof(console) !== "undefined") {
            
for (var i = 0; i < arguments.length; i++) {
                console.log(arguments[i]);
            }
        }
    };
    
})();

 

 

 

测试代码在这里,只要把上面的js另存为remarker2.js放在demo page同一目录下就Ok了。用firefox调试

 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd"
>
<html>
<head>
    
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
    
<title>template engine -reMarker </title>
    
<script type="text/javascript" src="reMarker2.js"></script>
</head>
<body>
    
<div id="tpl">
        
<pre id="tpl_pre"></pre>
    
</div>
    
<div id="text"></div>
</body>
<script type="text/javascript">
    
var html=['<p>name: ${name}</p>',
                
'<p>age: ${age}</p>',
                
'<p>sex: ${sex}</p>',
                
'<p>fav:</p><ul>',
                
'<#list fav as f>',
                    
'<li>${f}</li>',
                
'</#list>',
                
'</ul>',
                
'<#--list language as being-->',
                
'<p>language:</p><ul>',
                
'<#list language as being>',
                    
'<li>${being_index + 1} - ',
                    
'<span style="color:<#if being.level < 1 >red<#else>black</#if>">',
                    
'${being.name}: ${being.level}</span>',
                    
'</li>',
                
'</#list>',
                
'</ul>',
                
'<#if like><span style="color: red">Yes, we like it!</span>',
                
'<#else><span style="color: green">No, we don\'t like it!</span></#if>'].join('');
        var data ={
            name: "mark one",
            age: 28,
            sex: "male",
            fav: ["game", "swim", "programming"],
            language: [{
                name: "C#",
                level: 2
            }, {
                name: "javascript",
                level: 3
            }, {
                name: "java",
                level: 2
            }, {
                name: "python",
                level: 1
            }],
            like: true
        };

        var te;

        console.profile();
        var marker=new reMarker(html,data);
        te=marker.process()
        console.profileEnd();

        document.write(te)
        //marker.process();

</script>
</html>

 

 

 

 

作者: simon4545 发表于 2010-12-14 17:03 原文链接

评论: 5 查看评论 发表评论


最新新闻:
· 专访甲骨文高管:Java虚拟机将支持更多动态语言 公布移动版路线图(2010-12-14 22:57)
· 甲骨文宣布成立北京云计算解决方案中心(2010-12-14 22:49)
· 我们的宇宙不是唯一的(2010-12-14 22:22)
· 法国竞争局警告谷歌不要滥用市场主导地位(2010-12-14 22:21)
· 旅行者1号即将飞离太阳系(2010-12-14 22:20)

编辑推荐:WP7有约(二):课后作业

网站导航:博客园首页  我的园子  新闻  闪存  小组  博问  知识库