JS模板引擎实现

早的时候我得到服务器返回的json数据 然后用jQuery拼接起来生成DOM插入到页面中去。
但是对于复杂的数据,拼字符串是件很头疼的事,为了方便开发,而且更具有通用性

所以就出现了很多的模板引擎 例如Mustache doT juicer Underscore Handlebars 等。
然后国内就有了xtemplate(淘宝) artTemplate(腾讯) baiduTemplate(百度) 等。
用在Node.js的例如有Jade EJS Nunjucks 等。

功能大多相同,各有特色。主要看以下方面
1.文件大小
2.执行速度
3.语法简明/易用/灵活/自定义操作符
4.错误处理/调试

而我也想尝试弄一个 其实也不难,没好意思说自己的有什么好的 也许简单就是好吧

首先 看一个最简单的 利用正则表达式替换

var re = /<%([^%>]+)?%>/g; //它的意思是匹配<%开头 中间的子匹配是^%>有至少一次 然后匹配%>结束

然后对传递进来的参数 例如 var tpl=Hello, my name is <%name%>. 执行match = re.exec(tpl) 则match的结果如下

["<%name%>", "name", index: 21, input: "Hello, my name is <%name%>."]

match[1] 替换成我们想要的变量就可以了。

以上的对于简单的替换就足够了 但是还是过于简单了 例如我们想要执行for循环怎么办 如果能够在<%和%>之间直接使用Javascript代码就最好了

那么在JavaScript你一定听说过Function 它的构造方法可以传入一段字符串作为函数的内容,例如

var body = '(function(){return data;}())'; var func = new Function('myFun', 'data', body); console.log(func('', 1));

它会把最后一个参数作为函数体 前面的作为形参

所以我们打算把字符串传递给new Function()来执行。

所以应该分辨js代码 和 普通字符串

    var _compile = function (tpl, data) {
    var re = /<%([^%>]+)?%>/g,
            //应该被立即执行的语句
            reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g,
            code = 'var r=[];\n',
            //利用游标来记录当前位置match[index]
            cursor = 0;
    var add = function (line, js) {
        //对于满足reExp的应该立即执行 不被添加到程序字符串中去:变量插入:普通字符串
        js ? code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n' :
        code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n';
    };
    while (match = re.exec(tpl)) {
        //普通字符串
        add(tpl.slice(cursor, match.index));
        //js
        add(match[1], true); // <-- say that this is actually valid js
        cursor = match.index + match[0].length;
    }
    add(tpl.substr(cursor, tpl.length - cursor));
    code += 'return r.join("");'; // <-- return the result
    var render = new Function(code.replace(/[\r\t\n]/g, ''));
    //有内容直接render返回编译好的字符串
    if (data) {
        return render.apply(data);
    }
    //没有内容就返回一个函数可以接受一个参数
    var template = function (data) {
        return render.call(data);
    };
    return template;
};

测试一下

    var body = $(document.body);
   var template = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.I am good at <%for(var index in this.skills){%> <%this.skills[index]%> <%}%> </p>';
   var func = _compile(template);
    body.append(func({
    name: "BQ",
    profile: { age: 21 },
    skills: ["js", "html", "css"]
}));

最后渲染出来的如下

Hello, my name is BQ. I'm 21 years old.I am good at js html css

加入缓存机制

var cache = {};
var bTemplate = function (str, data) {
    var node = document.getElementById(str);
    //传入的是一个#id
    if (node) {
        if (cache[str]) {
            return cache[str];
        };
        //textarea或input则取value,其它情况取innerHTML
        var html = /^(textarea|input)$/i.test(node.nodeName) ? node.value : node.innerHTML;
        return _compile(html);

    } else {
        //是模板字符串,则生成一个函数
        //如果直接传入字符串作为模板,则可能变化过多,因此不考虑缓存
        return _compile(str);
    }
}

这样就高效了一些 但是我仍然没有考虑容错机制 我没想过出错。还应该把所有的函数以及变量放到一个作用域下

我完了应该思考如何实现自定义标签处理

参考资料

伯乐在线

另外的实现

posted @ 2014-10-05 16:55  强子~Developer  阅读(472)  评论(1编辑  收藏  举报