读mmTemplate by RubyLouvre
发布在tcdona js小记2014年5月8日view:2817
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

试着分析下正美大大的模板 https://github.com/RubyLouvre/mmTemplate/blob/master/mmTemplate.js

  1. 首先是一trim函数,把字符串两端的空白字符去掉
  2. 然后是根据id获取模板字符串,用ejs.compile(source)编译成模版函数
  3. 最后用data来调用模版函数

这3步骤中有一些细节处理

  • 用ejs.cache[id]缓存模版函数,防止重复编译
  • $(id, doc)[0] || doc.querySelectorAll(id)[0] || doc.getElementById(id.slice(1)) 降级使用dom选择器
  • 针对 script textarea标签内的文本进行unescape处理
  • opts对象用来配置 opts.doc 模板文档对象 opts.tid 模板缓存id:string opts.open tops.close 开始关闭标签 默认 浏览器端为:"<&&>" nodejs端为:"<%%>" opts.[helpername] 在模板内部使用的自定义辅助函数

于是主要就是ejs.compile函数的编译过程 首先对于起提供的模板例子

        <script type="tmpl" id="table_tmpl">
            <&= title() &>
            <table border=1 width="80%">
                <&- for(var i=0,tl = @trs.length,tr;i<tl;i++){  -&>
                    <&- tr = @trs[i]; -&>
                    <tr>
                        <td><&= tr.name;; &></td> <td><&= tr.age; &></td> <td><&= tr.sex || "男" &></td>
                    </tr>
                    <& } &>
            </table>
            <&# 怎么可能不支持图片 &>
            <img src="<&= @href &>">
        </script>

编译函数根据分割符号<&&> 将其分割,现在用|||为大家标记出来如下:

            |||= title() |||
            <table border=1 width="80%">
                |||- for(var i=0,tl = @trs.length,tr;i<tl;i++){  -|||
                    |||- tr = @trs[i]; -|||
                    <tr>
                        <td>|||= tr.name;; |||</td> <td>|||= tr.age; |||</td> <td>|||= tr.sex || "男" |||</td>
                    </tr>
                    ||| } |||
            </table>
            |||# 怎么可能不支持图片 |||
            <img src="|||= @href |||">

这些由|||分割的片段被分为 普通字符串js逻辑 两大类 分割方法是设置一个标志位 flag=true flag === true 的时候为普通字符串【默认值】 flag === false 的时候为js逻辑 由于<&&>是开始、闭合一一相对的,于是可以根据当前flag的值flag ? open : close去查找到下一个分隔符的开关类型和位置 普通js逻辑字符串分别被存放于 codes和*js*数组中

同时,按照字符的顺序同步进行的是把所有的字符串编译成为一个匿名函数 先来一个时间字符串

var time = new Date * 1; // 1399372159719

匿名函数anonymous最终调用形式为anonymous(codes, js, filters, helpers) 于是编译函数分别给 codes,js 映射了一个形式参数名 txt1399372159719, js1399372159719 现在匿名函数显示如下:

function anonymous(txt1399372159719, js1399372159719, filters, title ) {
  return function(data) {
    'use strict';
    try {
      var r = '',
      line1399372159719 = 0;

当遇到普通字符串的时候,会转换成语句

r += txt1399372159719[index];

到遇到辅助函数<&= title() &>时转换为:

r += title();

遇到<&- for(var i=0,tl = @trs.length,tr;i<tl;i++){ -&>为:

for (var i = 0, tl = data.trs.length, tr; i < tl; i++) {;

其他类推 最终函数【格式化后】为:

function anonymous(txt1399372159719, js1399372159719, filters, title /**/ ) {
  return function(data) {
    'use strict';
    try {
      var r = '',
      line1399372159719 = 0;;
      r += txt1399372159719[0];;
      line1399372159719 = 1;;
      r += title();;
      r += txt1399372159719[1];;
      line1399372159719 = 2;
      for (var i = 0, tl = data.trs.length, tr; i < tl; i++) {;
        r += txt1399372159719[2];;
        line1399372159719 = 3;
        tr = data.trs[i];;
        r += txt1399372159719[3];;
        line1399372159719 = 4;;
        r += tr.name;;;;
        r += txt1399372159719[4];;
        line1399372159719 = 5;;
        r += tr.age;;;
        r += txt1399372159719[5];;
        line1399372159719 = 6;;
        r += tr.sex || "男";;
        r += txt1399372159719[6];;
        line1399372159719 = 7;
      };
      r += txt1399372159719[7];;
      line1399372159719 = 8;;
      r += txt1399372159719[8];;
      line1399372159719 = 9;;
      r += data.href;;
      r += txt1399372159719[9];
      return r;
    } catch (e) {
      EJS.log(e);
      EJS.log(js1399372159719[line1399372159719 - 1])
    }
  }
}

注意到

  1. 生成的函数堪比手写但有一些冗余的分号;防止语句冲突问题
  2. trim=true的方式处理标签冗余空白,适用于不需要生成多余空白的情况
  3. @trs.length等语句会被转化为 data.trs.length,而不是简单的获取全局变量
  4. |过滤器会被收集起来,然后一个一个的执行顺序嵌套执行
  5. #注释语句将被完全忽略
  6. helpers是被数组化,然后按照形式参数一次被匿名函数调用的【参考title函数】
  7. 对于line1399372159719这个变量,我猜测是用于记录编译结果函数的行数的,具体还不是很明白,希望知道的同学可以帮忙解释一下

这部分的代码都位于js逻辑字符串分支的处理中 生成函数的方式为 Function.apply,这段代码可以欣赏一下

        var body = ["txt"+time,"js"+time, "filters"]

        var fn = Function.apply(Function, body.concat(helperNames,t) );
        // console.log(fn.toString())
        var args = [codes, js, EJS.filters];
        var compiled = fn.apply(this, args.concat(helpers));

最后编译函数被(缓存)返回,供我们调用 fn(data)

好厉害的代码,我还要打个饱嗝再慢慢消化下。 特别是对于

line1399372159719 这个变量的作用 还在疑惑中,希望有人可以帮忙解释下?

the End.

评论
发表评论
暂无评论
WRITTEN BY
tcdona
(love life, interesting oriented, sometimes, performance art)
TA的新浪微博
PUBLISHED IN
tcdona js小记

tcdona js小记

友情链接 大搜车前端团队博客
我的收藏