zoukankan      html  css  js  c++  java
  • JS模板引擎 :ArtTemplate (2)

    上一篇初略的介绍了一下javascript中的模板引擎,有兴趣的可以戳 这里  。

    这一篇将带着大家一起做一个简易的模板引擎,

    上一篇介绍到:模板引擎其实做的就是两件事。

    1. 根据一定的规则,解析我们所定义的模板
    2. 根据数据以及模板生成html(其实背后也是用的字符串拼接)

    那么,首先,我们要有一个模板,一份数据,以及想生成的结果。

    例如:模板:

    1 <script id="test" type="text/html">
    2     <p><%=title%></p>
    3     <p><%=msg%></p>
    4     <ul>
    5         <% for (var i = 0; i < list.length; i ++) { %>
    6         <li><%= list[i] %></li>
    7         <% } %>
    8     </ul>
    9 </script>
    View Code

    数据:

    <script>
        var data = {
            title: '基本例子',
            msg: "这是一个例子",
            list: ['文艺', '博客', '摄影', '电影', '民谣', '旅行', '吉他']
        };
        var html = template('test', data);
        document.getElementById('content').innerHTML = html;
    </script>
    View Code

    结果:

    现在,我们就来实现上面这个例子。

    首先,我们需要定义我们的template方法。

        var template = function (templateName, data) {
            return renderFile(templateName, data);
        };
    
        var renderFile = function (templateName, data) {
            var render = template.get(templateName);
            return render(data);
        };
    View Code

    然后,获取我们缓存的template 的render方法

    template.get = function (templateName) {
            var cache;
            if (defaults.cache && cacheStore[templateName]) {
                cache = cacheStore[templateName];
            } else if (typeof document === 'object') {
                // 加载模板并编译
                var elem = document.getElementById(templateName);
                if (elem) {
                    var source = elem.innerHTML;
                    cache = compile(source);
                    if (templateName && defaults.cache) {
                        cacheStore[templateName] = cache;
                    }
                }
            }
            return cache;
     };
    View Code

    上面的代码,相信大家也都看得懂,主要是对渲染方法进行缓存,以提升效率。接下来是其中最重要的编译方法。

        var compile = template.compile = function (source, options) {
            var options = extend(options, defaults);
            var render = compiler(source, options);
            return render;
        };
    
        var compiler = function (source, options) {
            var openTag = options.openTag;
            var closeTag = options.closeTag;
            var cache = options.cache;
    
            //此处开始解析模板,并生成渲染函数
            var headerCode = "'use strict';" + "
    " + "var ";
            var mainCode = "$out='';";
            var footerCode = "return new String($out);";
    
            var uniq = {};
    
            //对模板中html代码的处理
            var html = function (code) {
                if (code) {
                    code = "$out+=" + stringify(code) + ";" + "
    ";
                }
                return code;
            };
    
            //对模板中逻辑部分的处理
            var logic = function (code) {
                // 输出语句. 编码: <%=value%> 不编码:<%=#value%>
                if (code.indexOf('=') === 0) {
                    code = code.replace(/^=[=#]?|[s;]*$/g, '');
                    code = "$out+=" + code + ";";
                }
                // 提取模板中的变量名
                each(getVariable(code), function (name) {
                    // name 值可能为空,在安卓低版本浏览器下
                    if (!name || uniq[name]) {
                        return;
                    }
                    var value;
                    // 声明模板变量
                    value = "$data." + name;
                    headerCode += name + "=" + value + ",";
                    uniq[name] = true;
    
                });
                return code + "
    ";
            };
    
            each(source.split(closeTag), function (code) {
                //此时代码已被截取成两部分,一部分是纯html,
                //一部分是逻辑代码,即是包含在html<%logic%>html里面的部分
                code = code.split(openTag);
                var htmlStr = code[0];
    
                var logicStr = code[1];
    
                mainCode += html(htmlStr);
    
                if (code.length > 1 && logicStr) {
    
                    mainCode += logic(logicStr);
                }
            });
    
    
    
    
            var code = headerCode + mainCode + footerCode;
            try {
                var Render = new Function("$data", code);
                return Render;
    
            } catch (e) {
                e.temp = "function anonymous($data) {" + code + "}";
                throw e;
            }
    
    
        };
    View Code

    这个方法,便是将模板引擎中的内容解析,并生成渲染方法。

    生成之后的渲染方法大概是下面这个样子。

    (function($data,$filename) {
        'use strict';
        var i=$data.i,list=$data.list,$out='';$out+='<ul>
    ';
        for (var i = 0; i < list.length; i ++) {
            $out+='
            <li>';
            $out+= list[i].text;
            $out+='</li>
    ';
        }
        $out+='
    </ul>';
        return new String($out);
    })
    View Code

    模板引擎的解析细节:

    大家知道,想生成上面的方法,无非就是通过evel 或者Funciton 之类的方法,将一个字符串生成成一个function。

    这里采用的是  var Render = new Function("$data", code);来生成渲染方法。其中$data为参数,code为字符串。 Function方法的具体使用可以参考官方文档 点我

    那么,我们想要生成这个Render方法,最重要的事便是 拼接 code 了。

    拼接code时,headerCode 和 footerCode 是不变的,对任何模板都是一样的。所以重点工作在于中间的mainCode。

    这里,mainCode主要分为两部分,一部分是模板中的html部分,一部分是模板中的逻辑部分(也就是包含在<%><%>里面的)

    compiler通过分割<%>,将里面的逻辑部分和html部分分离出来,然后再分别处理。

    • html部分无需特殊处理,只需要拼接进mainCode就可以了。
    • logic部分,就需要特殊处理了。例如 例子中的title,这个值,我们需要判断是变量还是关键字(for if ..,这个稍后会讲如何提取),
      如果是变量,就必须先赋值:var title = $data.title,$data即为渲染方法的参数,也就是用户到时候会传进来的。
      然后再直接将title追加到 mainCode中去就可以了。

    至于logic部分,如何提取变量主要是通过正则表达式来提取。

    // 静态分析模板变量
        var KEYWORDS =
            // 关键字
            'break,case,catch,continue,debugger,default,delete,do,else,false'
            + ',finally,for,function,if,in,instanceof,new,null,return,switch,this'
            + ',throw,true,try,typeof,var,void,while,with'
    
                // 保留字
            + ',abstract,boolean,byte,char,class,const,double,enum,export,extends'
            + ',final,float,goto,implements,import,int,interface,long,native'
            + ',package,private,protected,public,short,static,super,synchronized'
            + ',throws,transient,volatile'
    
                // ECMA 5 - use strict
            + ',arguments,let,yield'
    
            + ',undefined';
    
        var REMOVE_RE = //*[wW]*?*/|//[^
    ]*
    |//[^
    ]*$|"(?:[^"\]|\[wW])*"|'(?:[^'\]|\[wW])*'|s*.s*[$w.]+/g;
    
        var SPLIT_RE = /[^w$]+/g;  //非数字字母下滑线和$符以外的其他字符
    
        //生成这样的正则,用于匹配关键字   /break|case|catch|continue|debugger|default|delete|do|else|false|finally|for|function|if|in|instanceof|new|null|return|switch|this|throw|true|try|typeof|var|void|while|with|abstract|boolean|byte|char|class|const|double|enum|export|extends|final|float|goto|implements|import|int|interface|long|native|package|private|protected|public|short|static|super|synchronized|throws|transient|volatile|arguments|let|yield|undefined/g
        var KEYWORDS_RE = new RegExp(["\b" + KEYWORDS.replace(/,/g, '\b|\b') + "\b"].join('|'), 'g');
        var NUMBER_RE = /^d[^,]*|,d[^,]*/g; //匹配数字开头或者逗号后紧跟着数字的,
        var BOUNDARY_RE = /^,+|,+$/g;  //匹配开头的一个或多个逗号以及结尾的 用于去除首尾的逗号
        var SPLIT2_RE = /^$|,+/; //匹配多个逗号,用于分割 类似 param1,param2,,param3=> ["param1","param2","param3"] ,/^$/是为了匹配防止空字符串被切割
    
    
        // 获取变量
        function getVariable(code) {
            return code
                .replace(REMOVE_RE, '')
                .replace(SPLIT_RE, ',') //匹配单词字符
                .replace(KEYWORDS_RE, '') //
                .replace(NUMBER_RE, '')
                .replace(BOUNDARY_RE, '')
                .split(SPLIT2_RE);
        }
    View Code

    这里,主要各个正则的用处都写了注释了。

    主要做了:

    1. 过滤掉系统关键字,
    2. 过滤掉数字开头的变量(不合法变量)
    3. 由于1,2部,此时可能首尾有逗号,故,去除首尾的逗号
    4. 根据一个或多个逗号,分隔成参数数组   如:param1,param2,,param3=> ["param1","param2","param3"]

    就这样,渲染方法就拼接完了。

    最后,这里只是实现了js模板中简易的功能。后期诸如helper,include,还会在继续讲。

    完整源码地址 : https://github.com/chen4342024/andyTemplate

    如果有哪一方面讲的有问题。望不吝指教~

    最后问个问题,电脑是win10系统,用什么博客编辑器比较好?官方推荐的window live writer 安装不了!!!

  • 相关阅读:
    1082 射击比赛 (20 分)
    1091 N-自守数 (15 分)
    1064 朋友数 (20 分)
    1031 查验身份证 (15 分)
    1028 人口普查 (20 分)
    1059 C语言竞赛 (20 分)
    1083 是否存在相等的差 (20 分)
    1077 互评成绩计算 (20 分)
    792. 高精度减法
    791. 高精度加法
  • 原文地址:https://www.cnblogs.com/chen4342024/p/4916070.html
Copyright © 2011-2022 走看看