最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于JavaScript寫(xiě)一款EJS模板引擎

 更新時(shí)間:2022年02月14日 10:15:09   作者:隱冬  
這篇文章主要介紹了基于JavaScript寫(xiě)一款EJS模板引擎,基本語(yǔ)法就是定義render函數(shù),接收html字符串,和data參數(shù),下面其他具體介紹,需要的朋友可以參考一下

1. 起因

部門(mén)最近的一次分享中,有人提出來(lái)要實(shí)現(xiàn)一個(gè)ejs模板引擎,突然發(fā)現(xiàn)之前似乎從來(lái)都沒(méi)有考慮過(guò)這個(gè)問(wèn)題,一直都是直接拿過(guò)來(lái)用的。那就動(dòng)手實(shí)現(xiàn)一下吧。本文主要介紹ejs的簡(jiǎn)單使用,并非全部實(shí)現(xiàn),其中涉及到options配置的部分直接省略了。如有不對(duì)請(qǐng)指出,最后歡迎點(diǎn)贊 + 收藏。

2. 基本語(yǔ)法實(shí)現(xiàn)

定義render函數(shù),接收html字符串,和data參數(shù)。

const render = (ejs = '', data = {}) => {

}

事例模板字符串如下:

<body>
? ? <div><%= name %></div>
? ? <div><%= age %></div>
</body>

可以使用正則將<%= name %>匹配出來(lái),只保留name。這里借助ES6的模板字符串。將name用${}包裹起來(lái)。

props中第2個(gè)值就是匹配到的變量。直接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()];
? ? });
}

3. Function函數(shù)

這里得到的html是一個(gè)模板字符串??梢酝ㄟ^(guò)Function將字符串編程可執(zhí)行的函數(shù)。當(dāng)然這里也可以使用eval,隨你。

<body>
? ? <div>${ name }</div>
? ? <div>${ age }</div>
</body>

Function是一個(gè)構(gòu)造函數(shù),實(shí)例化后返回一個(gè)真正的函數(shù),構(gòu)造函數(shù)的最后一個(gè)參數(shù)是函數(shù)體的字符串,前面的參數(shù)都為形式參數(shù)。比如這里傳入形參name,函數(shù)體通過(guò)console.log打印一句話(huà)。

const func = new Function('name', 'console.log("我是通過(guò)Function構(gòu)建的函數(shù),我叫:" + name)');
// 執(zhí)行函數(shù),傳入?yún)?shù)
func('yindong'); // 我是通過(guò)Function構(gòu)建的函數(shù),我叫:yindong

利用Function的能力可以將html模板字符串執(zhí)行返回。函數(shù)字符串編寫(xiě)return,返回一個(gè)拼裝好的模板字符串、

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);
}

4 with

這里render函數(shù)中props[1]的實(shí)際上是變量名稱(chēng),也就是name和age,可以替換成data[props[1].trim()],不過(guò)這樣寫(xiě)會(huì)有一些問(wèn)題,偷個(gè)懶利用with代碼塊的特性。

with語(yǔ)句用于擴(kuò)展一個(gè)語(yǔ)句的作用域鏈。換句人話(huà)來(lái)說(shuō)就是在with語(yǔ)句中使用的變量都會(huì)先在with中尋找,找不到才會(huì)向上尋找。

比如這里定義一個(gè)age數(shù)字和data對(duì)象,data中包含一個(gè)name字符串。with包裹的代碼塊中輸出的name會(huì)先在data中尋找,age在data中并不存在,則會(huì)向上尋找。當(dāng)然這個(gè)特性也是一個(gè)with不推薦使用的原因,因?yàn)椴淮_定with語(yǔ)句中出現(xiàn)的變量是否是data中。

const age = 18;
const data = {
? ? name: 'yindong'
}

with(data) {
? ? console.log(name);
? ? console.log(age);
}

這里使用with改造一下getHtml函數(shù)。函數(shù)體用with包裹起來(lái),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>

5. ejs語(yǔ)句

這里擴(kuò)展一下ejs,加上一個(gè)arr.join語(yǔ)句。

<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也是可以正常編譯的。因?yàn)槟0遄址С謅rr.join語(yǔ)法,輸出:

<body>
? ? <div>yindong</div>
? ? <div>18</div>
? ? <div>1--2--3--4</div>
</body>

如果ejs中包含forEach語(yǔ)句,就比較復(fù)雜了。此時(shí)render函數(shù)就無(wú)法正常解析。

<body>
? ? <div><%= name %></div>
? ? <div><%= age %></div>
? ? <% arr.forEach((item) => {%>
? ? ? ? <div><%= item %></div>
? ? <%})%>
</body>

這里分兩步來(lái)處理。仔細(xì)觀察可以發(fā)現(xiàn),使用變量值得方式存在=號(hào),而語(yǔ)句是沒(méi)有=號(hào)的??梢詫?duì)ejs字符串進(jìn)行第一步處理,將<%=變量替換成對(duì)應(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>

第二步比較繞一點(diǎn),可以將上面的字符串處理成多個(gè)字符串拼接。簡(jiǎn)單舉例,將a加上arr.forEach的結(jié)果再加上c轉(zhuǎn)換為,str存儲(chǔ)a,再拼接arr.forEach每項(xiàng)結(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);
}

替換后得到的字符串長(zhǎng)成這個(gè)樣子。

<body>
? ? <div>${ name }</div>
? ? <div>${ age }</div>
? ? `
?arr.forEach((item) => {
?str += `
? ? ? ? <div>${ item }</div>
? ? `
})
?str += `
</body>

添加換行會(huì)更容易看一些??梢园l(fā)現(xiàn),第一部分是缺少首部`的字符串,第二部分是用str存儲(chǔ)了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 = `,這樣就是一個(gè)完整的字串了,第二部分不需要處理,會(huì)再第一部分基礎(chǔ)上拼接上第二部分的執(zhí)行結(jié)果,第三部分需要在結(jié)尾出拼接`; return str; 也就是補(bǔ)齊尾部的模板字符串,并且通過(guò)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用于存儲(chǔ)第一部分的字符串,尾部通過(guò)return返回str字符串。

const getHtml = (html, data) => {
? ? const func = new Function('data', ` with(data) { let str = \`${html}\`; return str; }`);
? ? return func(data);
}

這樣就可以實(shí)現(xiàn)執(zhí)行ejs語(yǔ)句了。

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>

6. 標(biāo)簽轉(zhuǎn)義

<%=會(huì)對(duì)傳入的html進(jìn)行轉(zhuǎn)義,這里編寫(xiě)一個(gè)escapeHTML轉(zhuǎn)義函數(shù)。

const escapeHTML = (str) => {
? ? if (typeof str === 'string') {
? ? ? ? return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/ /g, "&nbsp;").replace(/"/g, "&#34;").replace(/'/g, "&#39;");
? ? } else {
? ? ? ? return str;
? ? }
}

變量替換的時(shí)候使用escapeHTML函數(shù)處理變量。這里通過(guò)\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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/ /g, "&nbsp;").replace(/"/g, "&#34;").replace(/'/g, "&#39;");
? ? ? ? ? ? } 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);
}

<%-會(huì)保留原本格式輸出,只需要再加一條不使用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>&lt;div&gt;escapeHTML&lt;/div&gt;</div>
</body>

至此一個(gè)簡(jiǎn)單的ejs模板解釋器就寫(xiě)完了。

到此這篇關(guān)于基于JavaScript寫(xiě)一款EJS模板引擎的文章就介紹到這了,更多相關(guān)寫(xiě)一款EJS模板引擎內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • javascript實(shí)現(xiàn)圖片輪播簡(jiǎn)單效果

    javascript實(shí)現(xiàn)圖片輪播簡(jiǎn)單效果

    這篇文章主要為大家詳細(xì)介紹了javascript實(shí)現(xiàn)圖片輪播簡(jiǎn)單效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • JavaScript 繼承 封裝 多態(tài)實(shí)現(xiàn)及原理詳解

    JavaScript 繼承 封裝 多態(tài)實(shí)現(xiàn)及原理詳解

    這篇文章主要介紹了JavaScript 繼承 封裝 多態(tài)實(shí)現(xiàn)及原理詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-07-07
  • JavaScript 中 avalon綁定屬性總結(jié)

    JavaScript 中 avalon綁定屬性總結(jié)

    avalon是前端MVVM框架,在js中經(jīng)常會(huì)用到。這篇文章主要介紹了JavaScript 中 avalon綁定屬性總結(jié)的相關(guān)資料,需要的朋友可以參考下
    2016-10-10
  • 一起來(lái)了解一下JavaScript的預(yù)編譯(小結(jié))

    一起來(lái)了解一下JavaScript的預(yù)編譯(小結(jié))

    這篇文章主要介紹了一起來(lái)了解一下JavaScript的預(yù)編譯(小結(jié)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • JavaScript避免代碼的重復(fù)執(zhí)行經(jīng)驗(yàn)技巧分享

    JavaScript避免代碼的重復(fù)執(zhí)行經(jīng)驗(yàn)技巧分享

    經(jīng)常會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題,那就是重復(fù)的代碼執(zhí)行,下面就是一些在查看它們的源代碼時(shí)發(fā)現(xiàn)一些問(wèn)題,把這些分享給大家,希望能讓你們更加簡(jiǎn)潔高效的寫(xiě)出JavaScript代碼
    2014-04-04
  • js+html5繪制圖片到canvas的方法

    js+html5繪制圖片到canvas的方法

    這篇文章主要介紹了js+html5繪制圖片到canvas的方法,涉及html5元素操作的相關(guān)技巧,需要的朋友可以參考下
    2015-06-06
  • 在頁(yè)面中js獲取光標(biāo)/鼠標(biāo)的坐標(biāo)及光標(biāo)的像素坐標(biāo)

    在頁(yè)面中js獲取光標(biāo)/鼠標(biāo)的坐標(biāo)及光標(biāo)的像素坐標(biāo)

    頁(yè)面JS光標(biāo)/鼠標(biāo)坐標(biāo),百度統(tǒng)計(jì)中有個(gè)熱點(diǎn)統(tǒng)計(jì)圖,我們要做的就是獲取光標(biāo)的像素坐標(biāo)
    2013-11-11
  • 微信小程序的開(kāi)發(fā)范式BeautyWe.js入門(mén)詳解

    微信小程序的開(kāi)發(fā)范式BeautyWe.js入門(mén)詳解

    這篇文章主要介紹了微信小程序的開(kāi)發(fā)范式BeautyWe.js詳解,它是一套專(zhuān)注于微信小程序的企業(yè)級(jí)開(kāi)發(fā)范式,它的愿景是:讓企業(yè)級(jí)的微信小程序項(xiàng)目中的代碼,更加簡(jiǎn)單、漂亮,需要的朋友可以參考下
    2019-07-07
  • 詳解Bootstrap按鈕

    詳解Bootstrap按鈕

    本文給大家介紹bootstrap按鈕相關(guān)知識(shí),包括按鈕大小、按鈕狀態(tài)、按鈕標(biāo)簽相關(guān)樣式定義,對(duì)bootstrap按鈕相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧
    2016-01-01
  • JavaScript+HTML5實(shí)現(xiàn)的日期比較功能示例

    JavaScript+HTML5實(shí)現(xiàn)的日期比較功能示例

    這篇文章主要介紹了JavaScript+HTML5實(shí)現(xiàn)的日期比較功能,涉及javascript結(jié)合HTML5針對(duì)日期的轉(zhuǎn)換與運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下
    2017-07-07

最新評(píng)論

上思县| 蛟河市| 永寿县| 新田县| 龙门县| 万盛区| 鹿泉市| 石渠县| 福建省| 上栗县| 迁西县| 勐海县| 华蓥市| 嘉禾县| 射阳县| 西乌珠穆沁旗| 武清区| 吴旗县| 九寨沟县| 天气| 横山县| 旌德县| 板桥市| 安顺市| 杨浦区| 仁化县| 乡宁县| 金堂县| 西吉县| 阳春市| 兴化市| 石门县| 德钦县| 郑州市| 田东县| 安多县| 台南县| 三河市| 广灵县| 建瓯市| 永川市|