國慶節過去第一天,有點懶,但一想到今年的法定節假日都已經過完,慢慢就燃起奮鬥慾望。今天碰巧看到網絡上一些高手博客寫着渲染模板教程,就做個隨筆記錄吧!
渲染模板簡單的說,就是將一些數據,字符串加載到幾個的變量當中。
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