日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術(shù)文章
文章詳情頁

只有 20 行的 JavaScript 模板引擎實例詳解

瀏覽:202日期:2023-10-28 14:39:54

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

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

(譯者吐槽:只收藏不點贊都是耍流氓)

前言

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

因此,我希望去寫一個可以滿足我當(dāng)前需求的模板引擎。AbsurdJS主要作為NodeJS的模塊使用,但同時它也可以在客戶端使用。為了這個目的,我無法使用市面上已經(jīng)存在的模板引擎,因為它們幾乎全都依賴于NodeJS,并且難以在瀏覽器中使用。我需要一個更小,純JS寫成的模板引擎。我瀏覽了這篇由John Resig寫的博客,似乎這正是我需要的東西。我把當(dāng)中的代碼稍作修改,并且濃縮到了20行。

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

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

這是我們在開始的時候?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}));

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

出現(xiàn)錯誤的原因,是當(dāng)我們在模板中輸入<%profile.age%>的時候,我們得到的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)建一個函數(shù)的方法去完成這個功能。舉個栗子:

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

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

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

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

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

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

var template = ’My skills:’ + ’<%for(var index in this.skills) {%>’ + ’<a href='http://www.b3g6.com/bcjs/16666.html'><%this.skills[index]%></a>’ +’<%}%>’;

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

return’My skills:’ + for(var index in this.skills) { +’<a href='http://www.b3g6.com/bcjs/16666.html'>’ + this.skills[index] +’</a>’ +}

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

var r = [];r.push(’My skills:’); for(var index in this.skills) {r.push(’<a href='http://www.b3g6.com/bcjs/16666.html'>’);r.push(this.skills[index]);r.push(’</a>’);}return r.join(’’);

下一步邏輯就是整理得到的每一行代碼以便生成函數(shù)。我們已經(jīng)從模板中提取出了一些信息,知道了標(biāo)識片段的內(nèi)容和位置,所以我們可以通過一個指針變量(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以聲明一個數(shù)組為開頭,作為整個函數(shù)的函數(shù)體。正如我所說的,指針變量cursor表示我們正處于模板的哪個位置,我們需要它去遍歷所有的字符串,跳過填充數(shù)據(jù)的片段。另外,add函數(shù)的任務(wù)是把字符串插入到code變量中,作為構(gòu)建函數(shù)體的過程方法。這里有一個棘手的地方,我們需要跳過標(biāo)識符<%%>,否則當(dāng)中的JS腳本將會失效。如果我們直接運行上述代碼,結(jié)果將會是下面的情況:

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.name和this.profile.age不應(yīng)該帶引號。我們改進(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)識片段中的內(nèi)容將通過一個boolean值進(jìn)行控制。現(xiàn)在我們得到了一個正確的函數(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('');

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

return new Function(code.replace(/[rtn]/g, ’’)).apply(data);

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

3、代碼優(yōu)化

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

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

結(jié)果將會報錯,錯誤為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='http://www.b3g6.com/bcjs/16666.html'>');r.push(this.skills[index]);r.push('</a>');r.push(});r.push('');return r.join('');

包含著for循環(huán)的代碼不應(yīng)該被push到數(shù)組當(dāng)中,而是直接放在腳本里面。為了解決這個問題,在把代碼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’;}

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

var r=[];r.push('My skills:');for(var index in this.skills) {r.push('<a href='http://www.b3g6.com/bcjs/16666.html#'>');r.push(this.skills[index]);r.push('</a>');}r.push('');return r.join('');

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

My skills:<a href='http://www.b3g6.com/bcjs/16666.html#' >js</a><a href='http://www.b3g6.com/bcjs/16666.html#'>html</a><a href='http://www.b3g6.com/bcjs/16666.html#'>css</a>

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

var template = ’My skills:’ + ’<%if(this.showSkills) {%>’ + ’<%for(var index in this.skills) {%>’ + ’<a href='http://www.b3g6.com/bcjs/16666.html#'><%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(/[rtn]/g, ’’)).apply(options);}

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

后記(譯者注)

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

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

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

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

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

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

標(biāo)簽: JavaScript
相關(guān)文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
国产精品一卡| 日本一区二区三区视频在线看| 精品网站999| 伊人影院久久| 亚洲免费福利| 日韩精选在线| 蜜臀久久99精品久久久画质超高清| yellow在线观看网址| 日本一区中文字幕| 怡红院精品视频在线观看极品| 伊人久久国产| 国产不卡av一区二区| 国产精品久久| 91成人小视频| 日韩av黄色在线| 日本强好片久久久久久aaa| 免费国产亚洲视频| 午夜在线精品| 亚洲精品欧洲| 国产一区二区三区自拍| 亚洲精品一级二级| 久久三级视频| 亚洲五月婷婷| 欧美午夜不卡| 亚洲欧美视频| 四虎成人精品一区二区免费网站| 亚洲专区欧美专区| 黑丝一区二区| 香蕉久久夜色精品国产| 亚洲伊人精品酒店| 天堂精品久久久久| 欧美日韩网址| 麻豆91在线播放| 天堂√8在线中文| 日产精品一区二区| 欧美午夜精彩| 日本欧美在线| 国产精品调教视频| 成人福利av| 天堂成人国产精品一区| 国产乱论精品| 国产精品伦理久久久久久| 亚洲精品1区| 国产精区一区二区| 久久精品成人| 日韩av成人高清| 高清久久一区| 亚洲欧美网站在线观看| 久久影院资源站| 红杏一区二区三区| 亚洲成人精选| 国产精品巨作av| 深夜福利视频一区二区| 视频一区日韩精品| 日韩在线欧美| 欧美日韩a区| 狠狠色综合网| 中文字幕在线视频网站| 免费在线观看成人| 日韩伦理一区| 欧美精品国产白浆久久久久| 日本韩国欧美超级黄在线观看| 日韩av一二三| 国产精品美女久久久| 国产日产高清欧美一区二区三区 | 视频一区欧美精品| 国产精品sss在线观看av| 在线视频日韩| 久久三级福利| 鲁大师精品99久久久| 欧美特黄一区| 日韩精品永久网址| 日本午夜精品久久久久| 欧美日韩国产免费观看| 欧美日韩国产观看视频| 欧美亚洲综合视频| 美女一区网站| 国产日韩免费| 欧美日本久久| 欧美午夜网站| 久久国产福利| 爽好多水快深点欧美视频| 蜜桃视频免费观看一区| 欧美日韩国产v| 国产精品v亚洲精品v日韩精品| 婷婷六月综合| 国产一区91| 美美哒免费高清在线观看视频一区二区 | 亚洲精品国产日韩| 日韩美女一区二区三区在线观看| 国产精品99久久免费| 国产极品久久久久久久久波多结野| 国产精品视频3p| 精品一区二区三区免费看| 国产精品中文字幕制服诱惑| 国产精品麻豆成人av电影艾秋| 国产精品自在| 国产美女高潮在线| 黄色在线一区| 日韩av三区| 国产日韩电影| 久久香蕉精品| 欧美日韩一区自拍| 久久久一二三| 今天的高清视频免费播放成人| 荡女精品导航| 蜜桃成人av| 国产精品一页| 亚洲理论在线| 国产精品亚洲欧美一级在线 | 欧洲亚洲一区二区三区| 国产综合激情| 国产午夜精品一区在线观看| 成人亚洲一区| 视频一区二区中文字幕| 精品免费视频| 三级在线观看一区二区| 久久av超碰| 亚洲小说欧美另类婷婷| 免费观看日韩电影| 欧洲激情综合| 91精品综合| 麻豆91小视频| 日韩国产欧美一区二区三区| 99久久视频| 久久av免费| 亚洲精品免费观看| 国产精品av久久久久久麻豆网| 国产精品天天看天天狠| 欧美在线亚洲| 日韩欧美精品| 美女视频免费精品| 日本欧美一区二区在线观看| 亚洲香蕉视频| 尤物tv在线精品| 日韩免费看片| 欧美激情 亚洲a∨综合| 日韩欧美午夜| 鲁大师精品99久久久| 国产激情一区| 久久香蕉精品香蕉| 国产精品地址| 国产精品调教| 国产日产精品_国产精品毛片| 亚洲另类视频| 亚洲精品九九| 日韩国产欧美三级| 日韩在线观看一区二区三区| 成人啊v在线| 亚洲成av在线| 欧美成人午夜| 亚洲伦乱视频| 免费欧美一区| 在线综合视频| 视频一区欧美日韩| 亚洲男女av一区二区| 激情久久中文字幕| 在线观看精品| 欧美午夜不卡| 亚洲毛片在线| 亚洲精品综合| 欧美日韩一区二区综合| 日韩国产一区| 日韩精品首页| 99国产精品久久久久久久成人热| 色一区二区三区四区| 日本在线高清| 亚洲成人二区| 伊人www22综合色| 欧美国产另类| 午夜久久免费观看| 日本a口亚洲| 国产精品啊啊啊| 国产99久久久国产精品成人免费| 日韩影片在线观看| 日韩激情视频网站| 亚洲黄色网址| 亚洲精品伊人| 国产精品永久| 欧美1区二区| 国产韩日影视精品| 国产精品日韩精品在线播放| 欧美sm一区| 国产探花一区| 久久精品中文| 91精品麻豆| 亚洲高清毛片| 精品午夜视频| 免费美女久久99| 美女av一区| 亚洲视频二区| 成人一区而且| 国产成人精选| 亚洲人成高清| 欧美精品一区二区三区精品| 国产剧情在线观看一区| 欧美亚洲综合视频| 丝袜美腿成人在线| 久久久久午夜电影| 精品视频一区二区三区在线观看|