最近項目中涉及到模板引擎,參考了一些博客文章進行了一些學(xué)習(xí),并在此進行記錄

1. 模板引擎是什么首先我們來了解什么是模板,模板就我個人理解而言其產(chǎn)生的目的是為了解決展示與數(shù)據(jù)的耦合,簡單來說" />

国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁 > 營銷資訊 > 網(wǎng)站運營 > 模板引擎的整理歸納

模板引擎的整理歸納

時間:2023-06-07 08:21:01 | 來源:網(wǎng)站運營

時間:2023-06-07 08:21:01 來源:網(wǎng)站運營

模板引擎的整理歸納:

最近項目中涉及到模板引擎,參考了一些博客文章進行了一些學(xué)習(xí),并在此進行記錄

1. 模板引擎是什么

首先我們來了解什么是模板,模板就我個人理解而言其產(chǎn)生的目的是為了解決展示與數(shù)據(jù)的耦合,簡單來說模板還是一段字符,只不過其中有一些片段跟數(shù)據(jù)相關(guān),實際開發(fā)中根據(jù)數(shù)據(jù)模型與模板來動態(tài)生成最終的HTML(或者其他類型片段,本文都以HTML 為例子)

而模板引擎就是可以簡化該拼接過程,通過一些聲明與語法或格式的工具,盡可能讓最終HTML的生成簡單且直觀

搬一下網(wǎng)上的概念:模板引擎(這里特指用于Web開發(fā)的模板引擎)是為了使用戶界面與業(yè)務(wù)數(shù)據(jù)(內(nèi)容)分離而產(chǎn)生的,它可以生成特定格式的文檔,用于網(wǎng)站的模板引擎就會生成一個標(biāo)準(zhǔn)的文檔。

模板引擎的核心原理就是兩個字:替換。將預(yù)先定義的標(biāo)簽字符替換為指定的業(yè)務(wù)數(shù)據(jù),或者根據(jù)某種定義好的流程進行輸出。







2. 不使用模板引擎的示例

這里我們通過一個例子來更加直白的了解模板引擎。

首先我們需要實現(xiàn)這樣的一個界面:







有如下要求: - 數(shù)據(jù)必須來源一個指定的數(shù)組 - 具有動態(tài)性,不能寫死數(shù)據(jù)

如果不使用模板引擎,希望最終HTML頁面跟數(shù)據(jù)綁定的話常見的實現(xiàn)有兩種。

字符串拼接

直接上相關(guān)代碼,其實就是將HTML作為字符串一個個拼出來:

var songs =[ {name:'剛剛好', singer:'薛之謙', url:'http://music.163.com/xxx'}, {name:'最佳歌手', singer:'許嵩', url:'http://music.163.com/xxx'}, {name:'初學(xué)者', singer:'薛之謙', url:'http://music.163.com/xxx'}, {name:'紳士', singer:'薛之謙', url:'http://music.163.com/xxx'}, {name:'我們', singer:'陳偉霆', url:'http://music.163.com/xxx'}, {name:'畫風(fēng)', singer:'后弦', url:'http://music.163.com/xxx'}, {name:'We Are One', singer:'郁可唯', url:'http://music.163.com/xxx'} ] //拼接字符串,有一定惡意腳本注入風(fēng)險 遍歷 var html = ''; html +='<div class="song-list">' html +=' <h1>熱歌榜</h1>' html +=' <ol>' for(var i=0;i<songs.length;i++){ html += '<li>'+songs[i].name+' - '+songs[i].singer+'</li>' } html +=' </ol>' html +='</div>' document.body.innerHTML =html;

構(gòu)造DOM對象

借助DOM對象和數(shù)據(jù)源來操作

// 構(gòu)造DOM對象 遍歷 缺點復(fù)雜; var elDiv = document.createElement('div') elDiv.className = 'song-list'; var elH1 =document.createElement('h1') elH1.appendChild(document.createTextNode('熱歌榜')) var elList = document.createElement('ol') for(var i = 0; i<songs.length;i++){ var li = document.createElement('li') li.textContent = songs[i].name +' - ' + songs[i].singer elList.appendChild(li) } elDiv.appendChild(elH1); elDiv.appendChild(elList); document.body.appendChild(elDiv);可以看到上述兩種方式雖然可以達成需求,但是尤其繁瑣且缺乏規(guī)范,很容易出錯。

我們這樣思考,其實這些數(shù)據(jù)替換的地方都是固定的也有一定的邏輯,那能不能將這個替換邏輯抽離出來形成規(guī)范,來統(tǒng)一進行處理呢?

3. 使用模板引擎的方式

置換型模板引擎

這種模板引擎原理比較直觀,實現(xiàn)也相對簡單,我們先來看一下:

var template = '<p>Hello,my name is <%name%>. I am <%age%> years old.</p>'; var data ={ name:'zyn', age:31 } var TemplateEngine = function (tpl,data){ var regex = /<%([^%>]+)?%>/g; while(match = regex.exec(tpl)){ tpl = tpl.replace(match[0],data[match[1]]) } return tpl } var string = TemplateEngine(template,data) console.log(string);這里其實就是把模板中需要替換的字符串做了個標(biāo)記,這里是以<%...%>作為標(biāo)記,然后替換時基于正則捕捉該標(biāo)記并進行數(shù)據(jù)源的替換(通過同一個key進行)

模板文件: var template = '<p>Hello,my name is <%name%>. I am <%age%> years old.</p>';數(shù)據(jù): var data ={ name:'zyn', age:31 }模板引擎: var TemplateEngine = function (tpl,data){ var regex = /<%([^%>]+)?%>/g; while(match = regex.exec(tpl)){ tpl = tpl.replace(match[0],data[match[1]]) } return tpl }HTML文件: var string=TemplateEngine(template,data) document.body.innerHTML= string

JS代碼函數(shù)型模板語法

上述方式存在一個問題,就是基本上以data["property"]方式來使用簡單對象傳遞數(shù)據(jù),但是如果對象是嵌套對象就有點難辦:

var data ={ name:'zyn', profile:{age:31} }在模板中使用<%profile.age%>的話,代碼會被替換成data[‘profile.age’],結(jié)果是undefined,因為括號型沒辦法認(rèn)識.符號,當(dāng)然我們可以改進Template函數(shù)來分解復(fù)雜對象轉(zhuǎn)換為[][]的形式。但是這里我們換一個方式。

這里我們思考是否一定要在標(biāo)記中寫key或者常規(guī)字符,能不能寫一段有邏輯的JS代碼進去,類似這樣:

var template = '<p>Hello, my name is <%this.name%>. I/'m <%this.profile.age%> years old.</p>'這里為了之后的示范,我們補充一下關(guān)于new Function的知識,這個函數(shù)的構(gòu)造函數(shù)可以根據(jù)傳入?yún)?shù)來動態(tài)生成一個函數(shù),包括函數(shù)入?yún)?,函?shù)體等:

var fn = new Function("num", "console.log(num + 1);");fn(2); //3等同于:

var fn = function(num) { console.log(num + 1);}fn(2); // 3這里我們思路基本明確了,就是希望構(gòu)建一個函數(shù)體字符串,然后利用JS代碼執(zhí)行過程幫我們把數(shù)據(jù)綁定到模板上面。

這里我們把所有字符串統(tǒng)一放到一個數(shù)組中,在程序最后將其拼接起來,然后借助new Function幫助我們處理JS邏輯:

var Arr=[];Arr.push("<p>Hello,my name is");Arr.push(this.name);Arr.push("i am");Arr.push(this.proflie.age)Arr.push("years old</p>")return Arr.join('')接下來需要做的還是去尋找模板中的標(biāo)記位,即<%...%>片段,然后遍歷所有的匹配項將其push到字符串?dāng)?shù)組中去,最后借助new Function完成。

我們來看下初步的代碼:

var TemplateEngine = function(tpl, data) { // 正則全局匹配 // code用于保存函數(shù)體字符串 // cursor是游標(biāo),用于記錄tpl處理的位置 var re = /<%([^%>]+)?%>/g, code = 'var Arr=[];/n', cursor = 0; // 函數(shù)add負責(zé)將解析的代碼行添加到code函數(shù)體中 // 后面的replace是將code包含的雙引號進行轉(zhuǎn)義 var add = function(line) { code += 'Arr.push("' + line.replace(/"/g, '//"') + '");/n'; } // 循環(huán)處理模板,每當(dāng)存在匹配項就進入循環(huá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 Arr.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>';var data = { name: "zyn", profile: { age: 29 }}console.log(TemplateEngine(template, data));循環(huán)過程:

第一次循環(huán):match=[ 0:<%this.name%>", 1:"this.name", index:21, input:"<p>Hello, my name is<%this.name%>.I'm<%this.profile.age%>years old.</p>", length:2 ]tpl.slice(cursor, match.index) = "<p>Hello, my name is "執(zhí)行函數(shù)add("<p>Hello, my name is ")code="var Arr=[];Arr.push("<p>Hello, my name is ");"在執(zhí)行add(match[1]);match[1]="this.name"code ="var Arr=[];Arr.push("<p>Hello, my name is ");Arr.push("this.name");" cursor = match.index + match[0].length;cursor = 21+13=34;//就是<%this.name%>最后一位的位置;第二次循環(huán)跟第一次一樣繼續(xù)把模板文件添加到code上;兩次循環(huán)完成后code = "var Arr[];Arr.push("<p>Hello, my name is ");Arr.push("this.name");Arr.push(". I'm ");Arr.push("this.profile.age")"cursor =60 ;然后執(zhí)行: add(tpl.substr(cursor, tpl.length - cursor));cursor =60 ; tpl.length=75 tpl.substr(cursor, tpl.length - cursor)截取最后一段模板文件 years old.</p>code += 'return Arr.join("");'code = "var Arr[];Arr.push("<p>Hello, my name is ");Arr.push("this.name");Arr.push(". I'm ");Arr.push("this.profile.age")Arr.push("years old </p>")return Arr.join("")"如果還不明白可以復(fù)制代碼在代碼上打幾個斷點看下執(zhí)行的過程,很快就能明白;最后我們會在控制臺里面看見如下的內(nèi)容:

var Arr[];Arr.push("<p>Hello, my name is ");Arr.push("this.name");Arr.push(". I'm ");Arr.push("this.profile.age")Arr.push("years old </p>")return Arr.join("")<p>Hello, my name is <%this.name%>. I'm <%this.profile.age%> years old.</p>這里還存在一些問題:

最后完善之后的如下:

var TemplateEngine = function(html, options) { var re = /<%([^%>]+)?%>/g, reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = 'var Arr=[];/n', cursor = 0; 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, ''));}這里感興趣的可以基于上面的示例自己嘗試去實現(xiàn)一下上一小節(jié)的例子~

dom-based模板引擎

dom-based模板引擎基本用于HTML相關(guān)領(lǐng)域,輸出模板直接是dom了(當(dāng)然,很多dom-based模板引擎也可以很方便的掛載string輸出端,從而在服務(wù)端也能輸出)

而輸入是沒有具體規(guī)定的,你可以基于也有的DOM樹,也可以是字符串(例如Angular)。還可以是自己定義的語言,只要你的模板引擎認(rèn)識就行了(例如React的JSX,JSX可以說是AST-based的,因為其不依賴DOM,這里就不區(qū)分那么細了)。

前者是需要模板引擎把字符串解析為AST,而后者就是定義了一套語法,給你語法糖讓你自己去寫AST了。得到AST后再解析得到模板語法,例如變量bind,循環(huán),條件判斷等。

dom-based模板引擎基本上不考慮輸出HTML/XML以外的東西

目前前端MVVM框架基本都內(nèi)置了相關(guān)的模板引擎用于快速且最小化完成DOM更新操作

4. 前端與后端的模板引擎渲染發(fā)展變化

上面介紹的基本圍繞模板引擎的實現(xiàn)原理和概念,下面主要分析一下目前模板引擎的應(yīng)用和發(fā)展階段,以及區(qū)別

發(fā)展階段

后端模板引擎渲染

最初模板引擎是放在后端的,那個時候靜態(tài)網(wǎng)頁居多,基本返回的都是后端拼接好的HTML,前端拿來直接渲染,然后再用JS進行一些交互處理就行。







該方式存在一些不足: - 前后端是在一個工程,不方便開發(fā)調(diào)試,與自動化測試 - 前端沒辦法使用自己的生態(tài) - 前后端職責(zé)混淆

但是該方式也擁有頁面渲染快,SEO友好,當(dāng)下不少純展示性網(wǎng)頁仍然使用該方式進行處理

客戶端渲染

隨著后續(xù)前端工程化以及前后端職責(zé)分離概念明確后,一系列前端MVVM框架也出現(xiàn)了,客戶端進行模板渲染漸漸成為主流。

此時后端只負責(zé)Model層處理,不再關(guān)心任何渲染相關(guān)內(nèi)容。

前后端解耦,數(shù)據(jù)通過ajax方式進行交互







優(yōu)勢顯而易見: - 前端獨立出來,可以充分使用各個生態(tài)與工具 - 更好管理 - 職責(zé)明確

仍有不足: - 首屏加載緩慢,因為要等JS加載完畢之后才能處理模板,渲染最終頁面 - SEO能力弱,因為html中基本都是模板信息,沒有啥實際內(nèi)容

node中間層

為了解決上述不足,便出現(xiàn)了node中間層概念。

整個流程變?yōu)椋簽g覽器 -> node -> 后端服務(wù)器 -> node -> 瀏覽器







一個典型的 node 中間層應(yīng)用就是后端提供數(shù)據(jù)、node 層渲染模板、前端動態(tài)渲染。

這個過程中,node 層由前端開發(fā)人員掌控,頁面中哪些頁面在服務(wù)器上就渲染好,哪些頁面在客戶端渲染,由前端開發(fā)人員決定。

這樣做,達到了以下的目的: - 保留后端模板渲染、首屏快速響應(yīng)、SEO 友好 - 保留前端后分離、客戶端渲染的功能(首屏服務(wù)器端渲染、其他客戶端渲染)

但這種方式也有一些不足:

服務(wù)器端渲染(SSR)

大部分情況下,服務(wù)器端渲染(SSR)與 node 中間層是同一個概念。只不過是在上文的基礎(chǔ)上加上前端組件化技術(shù),優(yōu)化服務(wù)器端的渲染,例如針對react或vue

react、vue、angular 等框架的出現(xiàn),讓前端組件化技術(shù)深入人心,但在一些需要首屏快速加載與 SEO 友好的頁面就陷入了兩難的境地了。 因為前端組件化技術(shù)天生就是給客戶端渲染用的,而在服務(wù)器端需要被渲染成 html 文本,這確實不是一件很容易的事,所以服務(wù)器端渲染(ssr)就是為了解決這個問題。

好在社區(qū)一直在不斷的探索中,讓前端組件化能夠在服務(wù)器端渲染,比如 next.js、nuxt.js、razzle、react-server、beidou 等。

一般這些框架都會有一些目錄結(jié)構(gòu)、書寫方式、組件集成、項目構(gòu)建的要求,自定義屬性可能不是很強。

以 next.js 為例,整個應(yīng)用中是沒有 html 文件的,所有的響應(yīng) html 都是 node 動態(tài)渲染的,包括里面的元信息、css, js 路徑等。渲染過程中,next.js 會根據(jù)路由,將首頁所有的組件渲染成 html,余下的頁面保留原生組件的格式,在客戶端渲染

使用建議

把模板引擎渲染的過程放在前端(客戶端)還是后端是要看具體應(yīng)用場景的。

如果你的網(wǎng)頁只是傳統(tǒng)展示型網(wǎng)頁,且需要SEO優(yōu)化,很少需要實時刷新,交互少,那么傳統(tǒng)的后端渲染模式還是可以使用的,再配合緩存,那么前端直接請求可以拿到最終頁面了。

另一方面,如果你不需要首屏快速加載,也不需要SEO優(yōu)化,那么可以選擇全客戶端渲染,開發(fā)方式最直觀

又或者你可以嘗試在需要首屏快速渲染與SEO的地方不適用react、vue等框架技術(shù),而在其他頁面使用這些框架進行純客戶端渲染。

最終如果你的技術(shù)團隊出色且支持,而且又需要快速渲染和SEO優(yōu)化,且用了react,vue等技術(shù),那你可以嘗試搭建SSR渲染架構(gòu)

5. 開源的模板引擎

這里簡單推薦幾個較優(yōu)秀的模板引擎,感興趣的可以自己看一下源碼繼續(xù)深入學(xué)習(xí)~ - Art-template - Jinja - pug

6. 參考博客

關(guān)鍵詞:歸納,整理,引擎,模板

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點擊下載Chrome瀏覽器
關(guān)閉