簡單的前端渲染模板實現

國慶節過去第一天,有點懶,但一想到今年的法定節假日都已經過完,慢慢就燃起奮鬥慾望。今天碰巧看到網絡上一些高手博客寫着渲染模板教程,就做個隨筆記錄吧!

渲染模板簡單的說,就是將一些數據,字符串加載到幾個的變量當中。

var tpl = 'Hei, my name is <%name%>, and I\'m <%age%> years old.';

js數據加載方式

var data = {
    "name": "Barret Lee",
    "age": "20"
};

var result = tplEngine(tpl, data);

以上使用方式,相比大家都很熟悉,目前成熟的渲染模板也有很多,包括一些mvc框架avaron ,angular,vue等都自帶前端渲染模板。所以大家也知道模板具有維護方便,代碼清晰,版本迭代都很方便。缺點就是seo 等,不過大家做項目時候,關於框架選擇,模板選擇基本上都是別人搞好,我們都是學着用就可以了。

所以我們就上面代碼,做個js編譯,我們要把<%name%>識別出來,然後將js數據寫進去,所以我們要使用正則表達式。

<div class="name">我的名字叫做<%name%></div>



    <script>
        var name = document.querySelector('.name').innerText;
        //數據源
        var data = {
            "name": "Barret Lee",
            "age": "20"
        };
        //正則獲取<%name%>中的name,並用data中的name的值替換
        var result = name.replace(/<%([^%>]+)?%>/g,function(s0,s1){
            return data[s1]
        });
        document.querySelector('.name').innerText = result;
    </script>

上面代碼使用正則獲取<%name%>變量,然後將data中的name值替換,精簡版js模板已經完成。嗯,哪怕這只是簡單的字符串替換。

由於時間關係,暫時這樣,明天有空繼續補上。

------------------------2017-10-10---------------------

繼續昨天代碼,當對象爲對象,字符串替換方式就失靈了,我們換一種方式去探索

<div class="render"><%name%> <%info.age%> </div>

    <script>
    let tmpl = document.querySelector('.render').innerText;
    let data = {
        "name": "Barret Lee",
        "info": { "age": "20"}
    }
    let result = tmpl.replace(/<%([^%>]+)?%>/g,function(s0,s1){
        return 'Hei, my name is ' + data.name + ', and I\'m ' + data.info.age+ 'years old.';
    })
    document.querySelector('.render').innerText = result;
    
    </script>

上面是得到變量之後,根據<%%>符號判斷,然後返回一個js對象值,是的,上面返回兩次,本來就有兩個變量~~

根據上面思路,我們繼續,假若存在for循環,我們要怎麼解析它的代碼?將for循環轉成字符串返回?嗯我們試試

let result = tmpl.replace(/<%([^%>]+)?%>/g,function(s0,s1){
      return 'Posts: ' + 
       'for(var i = 0; i < post.length; i++) {'
         '<a href="#">' + post[i].exper + '</a> }'
    })

這段代碼明顯不行,爲何?他會直接輸出該字符串,我們要的是得到多個a標籤和內容,所以我們需要分開保存,js數組上場了。

let result = [];
            result.push('Post');
            result.push('for(var i = 0; i < post.length; i++) {');
            result.push('<a href="#">');
            result.push(post[i].exper);
            result.push('</a>');
            result.push('}');
上面使用數組裝載for循環的語句和一些字符串,看起來完美,但事情沒那麼簡單,這樣子的數組都是字符串,無法轉換,我們換一下

var r = [];
r.push('Posts: ' );
for(var i = 0; i < post.length; i++) {
    r.push('<a href="#">');
    r.push(post[i].exper);
    r.push('</a>');
}
這樣子看來沒問題了,那麼我們怎麼解析運行呢?這裏要用到new Function()對象實例,可能很多人對function熟悉,但其實內部還是要經過new Function得到一個實例。我們可以使用另外一種方式創建函數

var function_name = new function(arg1, arg2, ..., argN, function_body)
在上面的形式中,每個 arg 都是一個參數,最後一個參數是函數主體(要執行的代碼)。這些參數必須是字符串。

是不是很熟悉?再看例子

function sayHi(sName, sMessage) {
  alert("Hello " + sName + sMessage);
}
上面是普通的不能在普通的函數聲明,那麼new Function的

var sayHi 
= 
new Function("sName", "sMessage", "alert(\"Hello \" + sName + sMessage);");

是不是很像?恩,一般情況下,我們很少使用new Function去聲明函數,因爲麻煩。對其感興趣可以看下這個鏈接  http://www.w3school.com.cn/js/pro_js_functions_function_object.asp

回到正題來,我們要用new Function構建一個函數來運行我們的數組,我們先來看下直接運行相關代碼樣子

var fn = new Function("data", 
    "var r = []; for(var i in data){ r.push(data[i]); } return r.join(' ')");
fn({"name": "barretlee", "age": "20"}); // barretlee 20

new function例子完整輸出

<div class="render">
            <% for(var i = 0; i < post.length; i++) {+
                <a href="#"><% post[i].expert %></a> + 
            <% } %>
        </div>
    
        <script>
            let tmpl = document.querySelector('.render').innerText;
            let data = {
                "name": "Barret Lee",
                "info": { "age": "20"}
            }
            
            var fn = new Function("data", "var r = []; for(var i in data){ r.push(data[i]); } return r.join(' ')");
            fn({"name": "barretlee", "age": "20"}); // barretlee 20
            document.querySelector('.render').innerText = fn({"name": "barretlee", "age": "20"});
        </script>

fn函數中傳入一個對象,返回一個字符串集合,join() 方法用於把數組中的所有元素放入一個字符串。元素是通過指定的分隔符進行分隔的。所以思路就有了,我們可以把邏輯部分和非邏輯部分的代碼鏈接成一個字符串,然後利用類似fn的函數直接編譯代碼。爲了能夠識別所有元素,我們要使用exec代替replace。

exec() 方法用於檢索字符串中的正則表達式的匹配,返回一個數組,其中存放匹配的結果。如果未找到匹配,則返回值爲 null。

以下爲w3cShool解釋

說明
exec() 方法的功能非常強大,它是一個通用的方法,而且使用起來也比 test() 方法以及支持正則表達式的 String 對象的方法更爲複雜。
如果 exec() 找到了匹配的文本,則返回一個結果數組。否則,返回 null。此數組的第 0 個元素是與正則表達式相匹配的文本,第 1 個元素是與 RegExpObject 的第 1 個子表達式相匹配的文本(如果有的話),第 2 個元素是與 RegExpObject 的第 2 個子表達式相匹配的文本(如果有的話),以此類推。除了數組元素和 length 屬性之外,exec() 方法還返回兩個屬性。index 屬性聲明的是匹配文本的第一個字符的位置。input 屬性則存放的是被檢索的字符串 string。我們可以看得出,在調用非全局的 RegExp 對象的 exec() 方法時,返回的數組與調用方法 String.match() 返回的數組是相同的。
但是,當 RegExpObject 是一個全局正則表達式時,exec() 的行爲就稍微複雜一些。它會在 RegExpObject 的 lastIndex 屬性指定的字符處開始檢索字符串 string。當 exec() 找到了與表達式相匹配的文本時,在匹配後,它將把 RegExpObject 的 lastIndex 屬性設置爲匹配文本的最後一個字符的下一個位置。這就是說,您可以通過反覆調用 exec() 方法來遍歷字符串中的所有匹配文本。當 exec() 再也找不到匹配的文本時,它將返回 null,並把 lastIndex 屬性重置爲 0。

http://www.w3school.com.cn/jsref/jsref_exec_regexp.asp

var reg = /<%([^%>]+)?%>/g;
            var tpl = 'Hei, my name is <%name%>, and I\'m <%age%> years old.';
            var match = reg.exec(tpl);
            console.log(match);

var reg = /<%([^%>]+)?%>/g;
while(match = reg.exec(tpl)) {
    console.log(match);
}

對比下兩者區別?第一種只會識別<%name%>,那麼<%age%>呢?提示:請注意,無論 RegExpObject 是否是全局模式,exec() 都會把完整的細節添加到它返回的數組中。這就是 exec() 與 String.match() 的不同之處,後者在全局模式下返回的信息要少得多。因此我們可以這麼說,在循環中反覆地調用 exec() 方法是唯一一種獲得全局模式的完整模式匹配信息的方法。

<div class="render">
                <% for(var i = 0; i < this.posts.length; i++) {%>
                    <a href="#"><% this.posts[i].expert %></a> 
                <% } %>
            </div>
        
            <script>
                var tmpl = document.querySelector('.render').innerText;
                var data = {
                    "posts": [{
                        "expert": "content 1",
                        "time": "yesterday"
                    },{
                        "expert": "content 2",
                        "time": "today"
                    },{
                        "expert": "content 3",
                        "time": "tomorrow"
                    },{
                        "expert": "",
                        "time": "eee"
                    }]
                };
                function tplEngine(tpl, data) {
                    var reg = /<%([^%>]+)?%>/g, 
                        regOut = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, 
                        code = 'var r=[];\n', 
                        cursor = 0;
        
                    var add = function(line, js) {
                        js? (code += line.match(regOut) ? line + '\n' : 'r.push(' + line + ');\n') :
                            (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
                        return add;
                    }
                    while(match = reg.exec(tpl)) {
                        add(tpl.slice(cursor, match.index))(match[1], true);
                        cursor = match.index + match[0].length;
                    }
                    add(tpl.substr(cursor, tpl.length - cursor));
                    code += 'return r.join("");';
                    return new Function(code.replace(/[\r\t\n]/g, '')).apply(data);
                };
                tplEngine(tmpl,data)
                document.querySelector('.render').innerHTML = tplEngine(tmpl,data);
            </script>

還有點沒搞懂,明天繼續




參考鏈接:http://www.cnblogs.com/hustskyking/p/principle-of-javascript-template.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章