欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

只有 20 行的 JavaScript 模板引擎實(shí)例詳解

 更新時(shí)間:2020年05月11日 09:32:48   作者:jrainlau  
這篇文章主要介紹了只有 20 行的 JavaScript 模板引擎,結(jié)合實(shí)例形式分析了JavaScript 模板引擎實(shí)現(xiàn)方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下

本文實(shí)例講述了 JavaScript 模板引擎。分享給大家供大家參考,具體如下:

原文鏈接:JavaScript template engine in just 20 lines

前言

我仍舊在為我的JS預(yù)處理器AbsurdJS進(jìn)行開發(fā)工作。它原本是一個(gè)CSS預(yù)處理器,但之后它擴(kuò)展成為了CSS/HTML預(yù)處理器,很快它將支持JS到CSS/HTML的轉(zhuǎn)換。它就像一個(gè)模板引擎一樣能夠生成HTML代碼,也就是說它能夠用數(shù)據(jù)填充模板當(dāng)中的標(biāo)識(shí)片段。

因此,我希望去寫一個(gè)可以滿足我當(dāng)前需求的模板引擎。AbsurdJS主要作為NodeJS的模塊使用,但同時(shí)它也可以在客戶端使用。為了這個(gè)目的,我無法使用市面上已經(jīng)存在的模板引擎,因?yàn)樗鼈儙缀跞家蕾囉贜odeJS,并且難以在瀏覽器中使用。我需要一個(gè)更小,純JS寫成的模板引擎。我瀏覽了這篇由John Resig寫的博客,似乎這正是我需要的東西。我把當(dāng)中的代碼稍作修改,并且濃縮到了20行。

這段代碼的運(yùn)行原理非常有趣,我將在這篇文章中一步一步為大家展示John的wonderful idea。

1、提取標(biāo)識(shí)片段

這是我們?cè)陂_始的時(shí)候?qū)⒁@得的東西:

var TemplateEngine = function(tpl, data) {
 // magic here ...
}
var template = '<p>Hello, my name is <%name%>. I\'m <%age%> years old.</p>';
console.log(TemplateEngine(template, {
 name: "Krasimir",
 age: 29
}));

一個(gè)簡(jiǎn)單的函數(shù),傳入模板和數(shù)據(jù)作為參數(shù),正如你所想象的,我們想要得到以下的結(jié)果:

<p>Hello, my name is Krasimir. I'm 29 years old.</p>

我們要做的第一件事就是獲取模板中的標(biāo)識(shí)片段<%...%>,然后用傳入引擎中的數(shù)據(jù)去填充它們。我決定用正則表達(dá)式去完成這些功能。正則不是我的強(qiáng)項(xiàng),所以大家將就一下,如果有更好的正則也歡迎向我提出。

var re = /<%([^%>]+)?%>/g;

我們將會(huì)匹配所有以<%開頭以%>結(jié)尾的代碼塊,末尾的g(global)表示我們將匹配多個(gè)。有許多的方法能夠用于匹配正則,但是我們只需要一個(gè)能夠裝載字符串的數(shù)組就夠了,這正是exec所做的工作:

var re = /<%([^%>]+)?%>/g;
var match = re.exec(tpl);

在控制臺(tái)console.log(match)可以看到:

[
 "<%name%>",
 " name ", 
 index: 21,
 input: 
 "<p>Hello, my name is <%name%>. I\'m <%age%> years old.</p>"
]

我們?nèi)〉昧苏_的匹配結(jié)果,但正如你所看到的,只匹配到了一個(gè)標(biāo)識(shí)片段<%name%>,所以我們需要一個(gè)while循環(huán)去取得所有的標(biāo)識(shí)片段。

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

運(yùn)行,發(fā)現(xiàn)所有的標(biāo)識(shí)片段已經(jīng)被我們獲取到了。

2、數(shù)據(jù)填充與邏輯處理

在獲取了標(biāo)識(shí)片段以后,我們就要對(duì)它們進(jìn)行數(shù)據(jù)的填充。使用.replace方法就是最簡(jiǎn)單的方式:

var TemplateEngine = function(tpl, data) {
 var re = /<%([^%>]+)?%>/g, match;
 while(match = re.exec(tpl)) {
  tpl = tpl.replace(match[0], data[match[1]])
 }
 return tpl;
}

data = {
 name: "Krasimir Tsonev",
 age: 29
}

OK,正常運(yùn)行。但很明顯這并不足夠,我們當(dāng)前的數(shù)據(jù)結(jié)構(gòu)非常簡(jiǎn)單,但實(shí)際開發(fā)中我們將面臨更復(fù)雜的數(shù)據(jù)結(jié)構(gòu):

{
 name: "Krasimir Tsonev",
 profile: { age: 29 }
}

出現(xiàn)錯(cuò)誤的原因,是當(dāng)我們?cè)谀0逯休斎?code><%profile.age%>的時(shí)候,我們得到的data["profile.age"]是undefined的。顯然.replace方法是行不通的,我們需要一些別的方法把真正的JS代碼插入到<%和%>當(dāng)中,就像以下栗子:

var template = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.</p>';

這看似不可能完成?John使用了new Function,即通過字符串去創(chuàng)建一個(gè)函數(shù)的方法去完成這個(gè)功能。舉個(gè)栗子:

var fn = new Function("arg", "console.log(arg + 1);");
fn(2); // 輸出 3

fn是個(gè)真正的函數(shù),它包含一個(gè)參數(shù),其函數(shù)體為console.log(arg + 1)。以上代碼等價(jià)于下列代碼:

var fn = function(arg) {
 console.log(arg + 1);
}
fn(2); // 輸出 3

通過new Function,我們得以通過字符串去創(chuàng)建一個(gè)函數(shù),這正是我們所需要的。在創(chuàng)建這么一個(gè)函數(shù)之前,我們需要去構(gòu)造這個(gè)它的函數(shù)體。該函數(shù)體應(yīng)當(dāng)返回一個(gè)最終拼接好了的模板。沿用前文的模板字符串,想象一下這個(gè)函數(shù)應(yīng)當(dāng)返回的結(jié)果:

return 
"<p>Hello, my name is " + 
this.name + 
". I\'m " + 
this.profile.age + 
" years old.</p>";

顯然,我們把模板分成了文本和JS代碼。正如上述代碼,我們使用了簡(jiǎn)單的字符串拼接的方式去獲取最終結(jié)果,但是這個(gè)方法無法100%實(shí)現(xiàn)我們的需求,因?yàn)橹笪覀冞€要處理諸如循環(huán)之類的JS邏輯,像這樣:

var template = 
'My skills:' + 
'<%for(var index in this.skills) {%>' + 
'<a href=""><%this.skills[index]%></a>' +
'<%}%>';

如果使用字符串拼接,結(jié)果將會(huì)變成這樣:

return
'My skills:' + 
for(var index in this.skills) { +
'<a href="">' + 
this.skills[index] +
'</a>' +
}

理所當(dāng)然這會(huì)報(bào)錯(cuò)。這也是我決定參照J(rèn)ohn的文章去寫邏輯的原因——我把所有的字符串都push到一個(gè)數(shù)組中,在最后才把它們拼接起來:

var r = [];
r.push('My skills:'); 
for(var index in this.skills) {
r.push('<a href="">');
r.push(this.skills[index]);
r.push('</a>');
}
return r.join('');

下一步邏輯就是整理得到的每一行代碼以便生成函數(shù)。我們已經(jīng)從模板中提取出了一些信息,知道了標(biāo)識(shí)片段的內(nèi)容和位置,所以我們可以通過一個(gè)指針變量(cursor)去幫助我們?nèi)〉米罱K的結(jié)果:

var TemplateEngine = function(tpl, data) {
 var re = /<%([^%>]+)?%>/g,
  code = 'var r=[];\n',
  cursor = 0, match;
 var add = function(line) {
  code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n';
 }
 while(match = re.exec(tpl)) {
  add(tpl.slice(cursor, match.index));
  add(match[1]);
  cursor = match.index + match[0].length;
 }
 add(tpl.substr(cursor, tpl.length - cursor));
 code += 'return r.join("");'; // <-- return the result
 console.log(code);
 return tpl;
}
var template = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.</p>';
console.log(TemplateEngine(template, {
 name: "Krasimir Tsonev",
 profile: { age: 29 }
}));

變量code以聲明一個(gè)數(shù)組為開頭,作為整個(gè)函數(shù)的函數(shù)體。正如我所說的,指針變量cursor表示我們正處于模板的哪個(gè)位置,我們需要它去遍歷所有的字符串,跳過填充數(shù)據(jù)的片段。另外,add函數(shù)的任務(wù)是把字符串插入到code變量中,作為構(gòu)建函數(shù)體的過程方法。這里有一個(gè)棘手的地方,我們需要跳過標(biāo)識(shí)符<%%>,否則當(dāng)中的JS腳本將會(huì)失效。如果我們直接運(yùn)行上述代碼,結(jié)果將會(huì)是下面的情況:

var r=[];
r.push("<p>Hello, my name is ");
r.push("this.name");
r.push(". I'm ");
r.push("this.profile.age");
return r.join("");

呃……這不是我們想要的。this.namethis.profile.age不應(yīng)該帶引號(hào)。我們改進(jìn)一下add函數(shù):

var add = function(line, js) {
 js? code += 'r.push(' + line + ');\n' :
  code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n';
}
var match;
while(match = re.exec(tpl)) {
 add(tpl.slice(cursor, match.index));
 add(match[1], true); // <-- say that this is actually valid js
 cursor = match.index + match[0].length;
}

標(biāo)識(shí)片段中的內(nèi)容將通過一個(gè)boolean值進(jìn)行控制?,F(xiàn)在我們得到了一個(gè)正確的函數(shù)體:

var r=[];
r.push("<p>Hello, my name is ");
r.push(this.name);
r.push(". I'm ");
r.push(this.profile.age);
return r.join("");

接下來我們要做的就是生成這個(gè)函數(shù)并且運(yùn)行它。在這個(gè)模板引擎的末尾,我們用以下代碼去代替直接返回一個(gè)tpl對(duì)象:

return new Function(code.replace(/[\r\t\n]/g, '')).apply(data);

我們甚至不需要向函數(shù)傳遞任何的參數(shù),因?yàn)?code>apply方法已經(jīng)為我們完整了這一步工作。它自動(dòng)設(shè)置了作用域,這也是為什么this.name可以運(yùn)行,this指向了我們的data。

3、代碼優(yōu)化

大致上已經(jīng)完成了。最后一件事情,我們需要支持更多復(fù)雜的表達(dá)式,像if/else表達(dá)式和循環(huán)等。讓我們用同樣的例子去嘗試運(yùn)行下列代碼:

var template = 
'My skills:' + 
'<%for(var index in this.skills) {%>' + 
'<a href="#"><%this.skills[index]%></a>' +
'<%}%>';
console.log(TemplateEngine(template, {
 skills: ["js", "html", "css"]
}));

結(jié)果將會(huì)報(bào)錯(cuò),錯(cuò)誤為Uncaught SyntaxError: Unexpected token for。仔細(xì)觀察,通過code變量我們可以找出問題所在:

var r=[];
r.push("My skills:");
r.push(for(var index in this.skills) {);
r.push("<a href=\"\">");
r.push(this.skills[index]);
r.push("</a>");
r.push(});
r.push("");
return r.join("");

包含著for循環(huán)的代碼不應(yīng)該被push到數(shù)組當(dāng)中,而是直接放在腳本里面。為了解決這個(gè)問題,在把代碼push到code變量之前我們需要多一步的判斷:

var re = /<%([^%>]+)?%>/g,
 reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g,
 code = 'var r=[];\n',
 cursor = 0;
var add = function(line, js) {
 js? code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n' :
  code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n';
}

我們添加了一個(gè)新的正則。這個(gè)正則的作用是,如果一段JS代碼以if, for, else, switch, case, break, |開頭,那它們將會(huì)直接添加到函數(shù)體中;如果不是,則會(huì)被push到code變量中。下面是修改后的結(jié)果:

var r=[];
r.push("My skills:");
for(var index in this.skills) {
r.push("<a href=\"#\">");
r.push(this.skills[index]);
r.push("</a>");
}
r.push("");
return r.join("");

理所當(dāng)然的正確執(zhí)行啦:

My skills:<a href="#" >js</a><a href="#">html</a><a href="#">css</a>

接下來的修改會(huì)給予我們更強(qiáng)大的功能。我們可能會(huì)有更加復(fù)雜的邏輯會(huì)放進(jìn)模板中,像這樣:

var template = 
'My skills:' + 
'<%if(this.showSkills) {%>' +
 '<%for(var index in this.skills) {%>' + 
 '<a href="#"><%this.skills[index]%></a>' +
 '<%}%>' +
'<%} else {%>' +
 '<p>none</p>' +
'<%}%>';
console.log(TemplateEngine(template, {
 skills: ["js", "html", "css"],
 showSkills: true
}));

進(jìn)行過一些細(xì)微的優(yōu)化之后,最終的版本如下:

var TemplateEngine = function(html, options) {
 var re = /<%([^%>]+)?%>/g, reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = 'var r=[];\n', cursor = 0, match;
 var add = function(line, js) {
  js? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
   (code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
  return add;
 }
 while(match = re.exec(html)) {
  add(html.slice(cursor, match.index))(match[1], true);
  cursor = match.index + match[0].length;
 }
 add(html.substr(cursor, html.length - cursor));
 code += 'return r.join("");';
 return new Function(code.replace(/[\r\t\n]/g, '')).apply(options);
}

優(yōu)化后的代碼甚至少于15行。

后記(譯者注)

這是我第一次完整地翻譯文章,語(yǔ)句多有錯(cuò)漏還請(qǐng)多多諒解,今后將繼續(xù)努力,爭(zhēng)取把更多優(yōu)質(zhì)的文章翻譯分享。

由于對(duì)前端的框架、模板引擎一類的工具特別感興趣,非常希望能夠?qū)W習(xí)當(dāng)中的原理,于是乎找了個(gè)相對(duì)簡(jiǎn)單的模板引擎開刀進(jìn)行研究,google后看到了這篇文章覺得非常優(yōu)秀,一步步講解生動(dòng)且深入,代碼經(jīng)過本人測(cè)試均能正確得到文章描述的結(jié)果。

模板引擎有多種設(shè)計(jì)思路,本文僅僅為其中的一種,其性能等參數(shù)還有待測(cè)試和提高,僅供學(xué)習(xí)使用。
謝謝大家~

感興趣的朋友可以使用在線HTML/CSS/JavaScript代碼運(yùn)行工具http://tools.jb51.net/code/HtmlJsRun測(cè)試上述代碼運(yùn)行效果。

更多關(guān)于JavaScript相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《javascript面向?qū)ο笕腴T教程》、《JavaScript錯(cuò)誤與調(diào)試技巧總結(jié)》、《JavaScript數(shù)據(jù)結(jié)構(gòu)與算法技巧總結(jié)》、《JavaScript遍歷算法與技巧總結(jié)》及《JavaScript數(shù)學(xué)運(yùn)算用法總結(jié)

希望本文所述對(duì)大家JavaScript程序設(shè)計(jì)有所幫助。

相關(guān)文章

  • 詳解JS函數(shù)重載

    詳解JS函數(shù)重載

    本文主要介紹了利用JavaScript中的特殊對(duì)象arguments來模擬函數(shù)重載的解決方案,非常的實(shí)用,給需要的小伙伴參考下
    2014-12-12
  • 讓網(wǎng)站自動(dòng)生成章節(jié)目錄索引的多個(gè)js代碼

    讓網(wǎng)站自動(dòng)生成章節(jié)目錄索引的多個(gè)js代碼

    這篇文章主要介紹了讓博客園博客自動(dòng)生成章節(jié)目錄索引的多個(gè)js代碼,需要的朋友可以參考下
    2018-01-01
  • JavaScript HTML DOM元素 節(jié)點(diǎn)操作匯總

    JavaScript HTML DOM元素 節(jié)點(diǎn)操作匯總

    這篇文章主要介紹了JavaScript HTML DOM元素 節(jié)點(diǎn)操作匯總,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-07-07
  • JavaScript使用Fetch的方法詳解

    JavaScript使用Fetch的方法詳解

    Fetch?API提供了一個(gè)JavaScript接口,用于訪問和操縱HTTP管道的部分。它還提供了一個(gè)全局?fetch()方法,該方法提供了一種簡(jiǎn)單,合理的方式來跨網(wǎng)絡(luò)異步獲取資源。本文將詳解JS如何使用Fetch,感興趣的可以學(xué)習(xí)一下
    2022-05-05
  • 關(guān)于img的href和src取變量及賦值的方法

    關(guān)于img的href和src取變量及賦值的方法

    這篇文章主要介紹了img的href和src取變量及賦值的方法,需要的朋友可以參考下
    2014-04-04
  • 利用jsonp解決js讀取本地json跨域的問題

    利用jsonp解決js讀取本地json跨域的問題

    這篇文章主要給大家介紹了關(guān)于利用jsonp解決js讀取本地json跨域的問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-12-12
  • JS實(shí)現(xiàn)日期加減的方法

    JS實(shí)現(xiàn)日期加減的方法

    這篇文章主要介紹了JS實(shí)現(xiàn)日期加減的方法,有需要的朋友可以參考一下
    2013-11-11
  • 微信小程序?qū)崿F(xiàn)多列選擇器

    微信小程序?qū)崿F(xiàn)多列選擇器

    這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)多列選擇器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • JS實(shí)現(xiàn)旋轉(zhuǎn)木馬輪播圖

    JS實(shí)現(xiàn)旋轉(zhuǎn)木馬輪播圖

    這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)旋轉(zhuǎn)木馬輪播圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-01-01
  • Javascript模塊模式分析

    Javascript模塊模式分析

    javascritp模式講解全局變量是魔鬼。在YUI中,我們僅用兩個(gè)全局變量:YAHOO和YAHOO_config。YUI的一切都是使用YAHOO對(duì)象級(jí)的成員或這個(gè)成員作用域內(nèi)的變量。我們建議在你的應(yīng)用程序也使用類似的規(guī)則。
    2008-05-05

最新評(píng)論