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';" + "\n" + "var ";
        var mainCode = "$out='';";
        var footerCode = "return new String($out);";

        var uniq = {};

        //對模板中html代碼的處理
        var html = function (code) {
            if (code) {
                code = "$out+=" + stringify(code) + ";" + "\n";
            }
            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 + "\n";
        };

        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>\n';
    for (var i = 0; i < list.length; i ++) {
        $out+='\n        <li>';
        $out+= list[i].text;
        $out+='</li>\n';
    }
    $out+='\n</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 = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|\s*\.\s*[$\w\.]+/g;

    var SPLIT_RE = /[^\w$]+/g;  //非數字字母下滑線和$符以外的其他字符

    //生成這樣的正則,用於匹配關鍵字   /\bbreak\b|\bcase\b|\bcatch\b|\bcontinue\b|\bdebugger\b|\bdefault\b|\bdelete\b|\bdo\b|\belse\b|\bfalse\b|\bfinally\b|\bfor\b|\bfunction\b|\bif\b|\bin\b|\binstanceof\b|\bnew\b|\bnull\b|\breturn\b|\bswitch\b|\bthis\b|\bthrow\b|\btrue\b|\btry\b|\btypeof\b|\bvar\b|\bvoid\b|\bwhile\b|\bwith\b|\babstract\b|\bboolean\b|\bbyte\b|\bchar\b|\bclass\b|\bconst\b|\bdouble\b|\benum\b|\bexport\b|\bextends\b|\bfinal\b|\bfloat\b|\bgoto\b|\bimplements\b|\bimport\b|\bint\b|\binterface\b|\blong\b|\bnative\b|\bpackage\b|\bprivate\b|\bprotected\b|\bpublic\b|\bshort\b|\bstatic\b|\bsuper\b|\bsynchronized\b|\bthrows\b|\btransient\b|\bvolatile\b|\barguments\b|\blet\b|\byield\b|\bundefined\b/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 安裝不了!!!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM