您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“怎么用JavaScript寫一款EJS模板引擎”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
部門最近的一次分享中,有人提出來要實現(xiàn)一個ejs模板引擎,突然發(fā)現(xiàn)之前似乎從來都沒有考慮過這個問題,一直都是直接拿過來用的。那就動手實現(xiàn)一下吧。本文主要介紹ejs
的簡單使用,并非全部實現(xiàn),其中涉及到options
配置的部分直接省略了。如有不對請指出,最后歡迎點贊 + 收藏。
定義render
函數(shù),接收html
字符串,和data
參數(shù)。
const render = (ejs = '', data = {}) => { }
事例模板字符串如下:
<body> <div><%= name %></div> <div><%= age %></div> </body>
可以使用正則將<%= name %>
匹配出來,只保留name
。這里借助ES6的模板字符串。將name用${}包裹起來。
props中第2個值就是匹配到的變量。直接props[1]替換。
[ '<%= name %>', ' name ', 16, '<body>\n <div><%= name %></div>\n <div><%= age %></div>\n</body>' ]
const render = (ejs = '', data = {}) => { const html = ejs.replace(/<%=(.*?)%>/g, (...props) => { return '${' + props[1] + '}'; // return data[props[1].trim()]; }); }
這里得到的html是一個模板字符串??梢酝ㄟ^Function
將字符串編程可執(zhí)行的函數(shù)。當(dāng)然這里也可以使用eval,隨你。
<body> <div>${ name }</div> <div>${ age }</div> </body>
Function
是一個構(gòu)造函數(shù),實例化后返回一個真正的函數(shù),構(gòu)造函數(shù)的最后一個參數(shù)是函數(shù)體的字符串,前面的參數(shù)都為形式參數(shù)。比如這里傳入形參name,函數(shù)體通過console.log
打印一句話。
const func = new Function('name', 'console.log("我是通過Function構(gòu)建的函數(shù),我叫:" + name)'); // 執(zhí)行函數(shù),傳入?yún)?shù) func('yindong'); // 我是通過Function構(gòu)建的函數(shù),我叫:yindong
利用Function
的能力可以將html模板字符串執(zhí)行返回。函數(shù)字符串編寫return,返回一個拼裝好的模板字符串、
const getHtml = (html, data) => { const func = new Function('data', `return \`${html}\`;`); return func(data); // return eval(`((data) => { return \`${html}\`; })(data)`) } const render = (ejs = '', data = {}) => { const html = ejs.replace(/<%=(.*?)%>/g, (...props) => { return '${' + props[1] + '}'; }); return getHtml(html, data); }
這里render函數(shù)中props[1]的實際上是變量名稱,也就是name和age,可以替換成data[props[1].trim()],不過這樣寫會有一些問題,偷個懶利用with代碼塊的特性。
with語句用于擴(kuò)展一個語句的作用域鏈。換句人話來說就是在with語句中使用的變量都會先在with中尋找,找不到才會向上尋找。
比如這里定義一個age數(shù)字和data對象,data中包含一個name字符串。with包裹的代碼塊中輸出的name會先在data中尋找,age在data中并不存在,則會向上尋找。當(dāng)然這個特性也是一個with不推薦使用的原因,因為不確定with語句中出現(xiàn)的變量是否是data中。
const age = 18; const data = { name: 'yindong' } with(data) { console.log(name); console.log(age); }
這里使用with
改造一下getHtml
函數(shù)。函數(shù)體用with包裹起來,data就是傳入的參數(shù)data,這樣with體中的所有使用的變量都從data中查找了。
const getHtml = (html, data) => { const func = new Function('data', `with(data) { return \`${html}\`; }`); return func(data); // return eval(`((data) => { with(data) { return \`${html}\`; } })(data)`) } const render = (ejs = '', data = {}) => { // 優(yōu)化一下代碼,直接用$1替代props[1]; // const html = ejs.replace(/<%=(.*?)%>/g, (...props) => { // return '${' + props[1] + '}'; // }); const html = ejs.replace(/<%=(.*?)%>/gi, '${$1}'); return getHtml(html, data); }
這樣就可以打印出真是的html了。
<body> <div>yindong</div> <div>18</div> </body>
這里擴(kuò)展一下ejs,加上一個arr.join語句。
<body> <div><%= name %></div> <div><%= age %></div> <div><%= arr.join('--') %></div> </body>
const data = { name: "yindong", age: 18, arr: [1, 2, 3, 4] } const html = fs.readFileSync('./html.ejs', 'utf-8'); const getHtml = (html, data) => { const func = new Function('data', ` with(data) { return \`${html}\`; }`); return func(data); } const render = (ejs = '', data = {}) => { const html = html = ejs.replace(/<%=(.*?)%>/gi, '${$1}'); return getHtml(html, data); } const result = render(html, data); console.log(result);
可以發(fā)現(xiàn)ejs也是可以正常編譯的。因為模板字符串支持arr.join語法,輸出:
<body> <div>yindong</div> <div>18</div> <div>1--2--3--4</div> </body>
如果ejs中包含forEach語句,就比較復(fù)雜了。此時render
函數(shù)就無法正常解析。
<body> <div><%= name %></div> <div><%= age %></div> <% arr.forEach((item) => {%> <div><%= item %></div> <%})%> </body>
這里分兩步來處理。仔細(xì)觀察可以發(fā)現(xiàn),使用變量值得方式存在=號,而語句是沒有=號的??梢詫js字符串進(jìn)行第一步處理,將<%=變量替換成對應(yīng)的變量,也就是原本的render
函數(shù)代碼不變。
const render = (ejs = '', data = {}) => { const html = ejs.replace(/<%=(.*?)%>/gi, '${$1}'); console.log(html); }
<body> <div>${ name }</div> <div>${ age }</div> <% arr.forEach((item) => {%> <div>${ item }</div> <%})%> </body>
第二步比較繞一點,可以將上面的字符串處理成多個字符串拼接。簡單舉例,將a加上arr.forEach
的結(jié)果再加上c轉(zhuǎn)換為,str存儲a,再拼接arr.forEach
每項結(jié)果,再拼接c。這樣就可以獲得正確的字符串了。
// 原始字符串 retrun ` a <% arr.forEach((item) => {%> item <%})%> c ` // 拼接后的 let str; str = `a`; arr.forEach((item) => { str += item; }); str += c; return str;
在第一步的結(jié)果上使用/<%(.*?)%>/g
正則匹配出<%%>中間的內(nèi)容,也就是第二步。
const render = (ejs = '', data = {}) => { // 第一步 let html = ejs.replace(/<%=(.*?)%>/gi, '${$1}'); // 第二步 html = html.replace(/<%(.*?)%>/g, (...props) => { return '`\r\n' + props[1] + '\r\n str += `'; }); console.log(html); }
替換后得到的字符串長成這個樣子。
<body> <div>${ name }</div> <div>${ age }</div> ` arr.forEach((item) => { str += ` <div>${ item }</div> ` }) str += ` </body>
添加換行會更容易看一些??梢园l(fā)現(xiàn),第一部分是缺少首部`的字符串,第二部分是用str存儲了forEach
循環(huán)內(nèi)容的完整js部分,并且可執(zhí)行。第三部分是缺少尾部`的字符串。
<body> <div>${ name }</div> <div>${ age }</div> ` // 第二部分 arr.forEach((item) => { str += ` <div>${ item }</div> ` }) // 第三部分 str += ` </body>
處理一下將字符串補(bǔ)齊,在第一部分添加let str = `,這樣就是一個完整的字串了,第二部分不需要處理,會再第一部分基礎(chǔ)上拼接上第二部分的執(zhí)行結(jié)果,第三部分需要在結(jié)尾出拼接`; return str; 也就是補(bǔ)齊尾部的模板字符串,并且通過return返回str完整字符串。
// 第一部分 let str = `<body> <div>${ name }</div> <div>${ age }</div> ` // 第二部分 arr.forEach((item) => { str += ` <div>${ item }</div> ` }) // 第三部分 str += ` </body> `; return str;
這部分邏輯可以在getHtml
函數(shù)中添加,首先在with中定義str用于存儲第一部分的字符串,尾部通過return返回str字符串。
const getHtml = (html, data) => { const func = new Function('data', ` with(data) { let str = \`${html}\`; return str; }`); return func(data); }
這樣就可以實現(xiàn)執(zhí)行ejs語句了。
const data = { name: "yindong", age: 18, arr: [1, 2, 3, 4], html: '<div>html</div>', escape: '<div>escape</div>' } const html = fs.readFileSync('./html.ejs', 'utf-8'); const getHtml = (html, data) => { const func = new Function('data', ` with(data) { var str = \`${html}\`; return str; }`); return func(data); } const render = (ejs = '', data = {}) => { // 替換所有變量 let html = ejs.replace(/<%=(.*?)%>/gi, '${$1}'); // 拼接字符串 html = html.replace(/<%(.*?)%>/g, (...props) => { return '`\r\n' + props[1] + '\r\n str += `'; }); return getHtml(html, data); } const result = render(html, data); console.log(result);
輸出結(jié)果:
<body>
<div>yindong</div>
<div>18</div><div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</body>
<%=會對傳入的html進(jìn)行轉(zhuǎn)義,這里編寫一個escapeHTML轉(zhuǎn)義函數(shù)。
const escapeHTML = (str) => { if (typeof str === 'string') { return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/ /g, " ").replace(/"/g, """).replace(/'/g, "'"); } else { return str; } }
變量替換的時候使用escapeHTML
函數(shù)處理變量。這里通過\s*去掉空格。為了避免命名沖突,這里將escapeHTML
改造成自執(zhí)行函數(shù),函數(shù)參數(shù)為$1變量名。
const render = (ejs = '', data = {}) => { // 替換轉(zhuǎn)移變量 // let html = ejs.replace(/<%=\s*(.*?)\s*%>/gi, '${escapeHTML($1)}'); let html = ejs.replace(/<%=\s*(.*?)\s*%>/gi, `\${ ((str) => { if (typeof str === 'string') { return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/ /g, " ").replace(/"/g, """).replace(/'/g, "'"); } else { return str; } })($1) }`); // 拼接字符串 html = html.replace(/<%(.*?)%>/g, (...props) => { return '`\r\n' + props[1] + '\r\n str += `'; }); return getHtml(html, data); }
getHtml
函數(shù)不變。
const getHtml = (html, data) => { const func = new Function('data', `with(data) { var str = \`${html}\`; return str; }`); return func(data); }
<%-會保留原本格式輸出,只需要再加一條不使用escapeHTML
函數(shù)處理的就可以了。
const render = (ejs = '', data = {}) => { // 替換轉(zhuǎn)義變量 let html = ejs.replace(/<%=\s*(.*?)\s*%>/gi, '${escapeHTML($1)}'); // 替換其余變量 html = html.replace(/<%-(.*?)%>/gi, '${$1}'); // 拼接字符串 html = html.replace(/<%(.*?)%>/g, (...props) => { return '`\r\n' + props[1] + '\r\n str += `'; }); return getHtml(html, data, escapeHTML); }
輸出樣式:
<body>
<div>yindong</div>
<div>18</div><div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div><div>escapeHTML</div></div>
</body>
至此一個簡單的ejs模板解釋器就寫完了。
“怎么用JavaScript寫一款EJS模板引擎”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。