读mmTemplate by RubyLouvre

试着分析下正美大大的模板
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逻辑字符串分别被存放于 codesjs数组中

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

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.

    原文作者:tcdona
    原文地址: https://segmentfault.com/a/1190000000498381
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞