模板引擎負責組裝數據,以另一種形式或外觀展示數據。 瀏覽器中的頁面是 Web 模板引擎最終的展示。javascript
不管你是否直接使用模板引擎,Web 模板一直都在,不在前端就在後端,它的出現甚至能夠追溯到超文本標記語言 HTML 標準正式確立以前。html
服務器端的模板引擎前端
我所知道最先的 Web 模板引擎是 PHP,它正式誕生於 1997 年,工做在服務器端。讓咱們看看 PHP 官方的 intro-whatis:java
HPer 廣泛贊同 PHP 自己就是最自然、原生的 PHP 模板引擎,由於她原本就是。在 PHP 的世界裏屢次出現過再包裝的模板引擎,著名的有 smarty。git
其它服務器端語言不少都有 HTML 模板引擎,好比 JSP、mustache。github
毫無疑問,這些服務器端模板引擎最終生成的結果是 HTML(XML) 字符串,處理流程邏輯使用宿主語言自己的語法實現。web
它們的共同特徵:HTML 只是個字符串, 最終結果可能還須要相似 Tidy 這樣的清潔或修正驗證工具。後端
這裏提出一個問題:二次封裝的 smarty 有存在的必要麼?瀏覽器
瀏覽器端的模板引擎bash
我所知道最先的前端模板引擎是 jCT,它託管於 Google Code,誕生於 2008 年,宿主語言是 JavaScript,工做在瀏覽器中。
今天在 OSC 搜索 JavaScript 模板引擎你會獲得 100+ 個結果,下邊列舉一些:
輕量度:tpl.js、T.js 認知度:arttemplate、mustache.js、doT.js、handlebars.js、pug DOM-tree-based:domTemplate、transparency、plates VDOM-based:htmltemplate-vdom、virtual-stache、html-patcher 流行框架:Vue.js、ReactJS、riot Real-DOM:PowJS
它們的共同特徵:全都支持插值。
這裏還有 templating-engines 受歡迎度的對比,甚至 best-javascript-templating-engines 投票及正反方的理由。
如何選擇
我認爲存在即合理,每一個引擎、框架總有可取之處,至少在你的應用裏,在某個時代,因此本文不會評論某個引擎哪一點很差,那樣是不客觀的。如今回答前邊提到的問題:smarty 有存在的必要麼?個人答案是:有。理由很簡單,看給誰用、看大背景。對於先後端沒有分離的應用,或前端人員對後端語言不夠熟悉,或因崗位職責須要,那麼前端人員掌握一種比較通用的模板語法(語言)是現實的,反之讓 PHPer 本身去使用 smarty 那就太浪費技能了。
下面是一般意義上的引擎選擇建議:
前提,選擇的引擎能知足數據渲染需求,且不和現有依賴衝突,若是你已經很是熟悉某個引擎,那你已經有答案了。
是一次性的項目需求麼? 是的話直接選擇輕量的,學習複雜度最低的。 是要作組件開發麼?
引擎支持預編譯結果,沒必要每次都實時編譯麼?
要跨平臺麼? 有官方提供支持的,首選類 React-JSX 的引擎或純粹的 VDOM 引擎。
選擇學習或維護複雜度最低的,衆所周知,開發者對調試的時間超過寫代碼的時間深惡痛絕。
最後纔是性能對比,性能對比是一件很是細緻的工做,他人的對比結果不必定符合你的場景。
我認爲應該弱化語法風格的對比,偏好是沒有可比性的,一些語法甚至有特殊的背景緣由。
爲何最後纔是性能對比?
性能的確很重要,但若是性能尚未影響到你的應用體驗度,那就忽視它。很難真實地模擬應用場景,一般只有經過真實場景來檢驗,目前的測試工具還達不到這種效果。
前述問題有些有固定答案,下面討論餘下的問題:如何考慮組件開發、支持預編譯、複雜度?
組件開發
進行組件開發已經再也不是選擇模板引擎的問題了,這是生態環境選擇的問題。若是你的應用須要更快地完成,那麼時間點是第一位的,就選擇流行框架,有足夠多的組件讓你使用或參考。若是你的應用有獨立的生態環境,須要技術選型以便長期維護,那繼續看下文。
預編譯
預編譯應該具有:
編譯結果在目標環境中再也不須要編譯過程。 編譯結果可調試性,這意味着結果應該包含原生 ECMAScript 代碼,而不是純粹的數據描述。 你們都知道 React-JSX 是支持預編譯的,官方的說法是 React Without JSX,即老是 build 過的。
一些基於字符串處理的引擎也支持預編譯。若是你須要預編譯,建議拋棄編譯結果依然是基於字符串拼接的引擎,那樣還不如不預編譯,那是 HTML5 未被普遍支持以前的技術手段。
至少也要有相似 React-JSX 這樣的編譯結果才具備可調試性。備註:Vue.js 支持多種模板引擎,可達到一樣的效果。
原 ReactJS 代碼,其中用到了 Web Components 技術:
class HelloMessage extends React.Component {
render() {
return <div>Hello <x-search>{this.props.name}</x-search>!</div>;
}
}
複製代碼
編譯後:
class HelloMessage extends React.Component {
render() {
return React.createElement(
"div",
null,
"Hello ",
React.createElement(
"x-search",
null,
this.props.name
),
"!"
);
}
}
複製代碼
很多 VDOM 引擎也能夠編譯相似效果,好比 htmltemplate-vdom。
<script>
var id = 3;
var env = {
people: [
{
id: 'id1',
name: 'John',
inner: [{ title: 'a1' }, { title: 'b1' }],
city: 'New York',
active: true
},
{
id: 'id2',
name: 'Mary',
inner: [{ title: 'a2' }, { title: 'b2' }],
city: 'Moscow'
}
],
githubLink: 'https://github.com/agentcooper/htmltemplate-vdom',
itemClick: function(id) {
env.people.forEach(function(person) {
person.active = String(person.id) === String(id);
});
loop.update(env);
}
// Omitted ....
};
</script>
複製代碼
複雜度
很難用惟一的標準去評判兩個引擎哪一個複雜度低,這是由使用者的思惟模式不一樣形成的。例如前邊列出的引擎在使用上以及預編譯結果上的區別,不一樣使用者感觸是不一樣的,這正是不一樣引擎存在的合理性、價值性。
有的使用者認爲這個應用場景有字符串模板就知足了需求,輕量夠用。 有的使用者認爲字符串拼接技術的模板引擎不夠強壯,不夠時代感。 有的使用者認爲OOP 夠理性,夠邏輯,夠抽象。 有的使用者認爲原生 HTML 才叫前端。 有的使用者認爲 VDOM 適用性更廣。
這些評判都有各自的理由,着眼點不一樣,標準也就不一樣了。可是咱們仍是能夠從它們的共性去考慮它們的複雜度。
字符串類模板一般都很輕量,不在本節討論範圍以內。對於非字符串模板複雜度評判的共性標準是什麼?我認爲,能夠考量數據綁定的複雜度。
本文所指的數據綁定不僅是插值,還包括上下文以及事件,甚至是整個運行期的宿主環境。
事實上至少須要達到 VDOM 級別的引擎才具備這種能力,由於經過 VDOM 能夠映射到真實的 DOM 節點。
大概有幾種模式(組合):
1.入口參數是個 Object,模板中的變量 x 是該對象的 .x 屬性,例:virtual-stache-example 2.特定語法或屬性,好比:Vue.js 的 ...,屬性 computed、methods 3.抽象的語義化屬性,好比:Vue.js 的 active 這個詞適用於多種場景,容易理解且不產生歧義 4.不負責綁定,須要使用者很是熟悉原生方法,用原生方法進行綁定,好比:PowJS
這些模式只是理論方面的,一般是模板引擎設計者要解決的問題。對於使用者來講不如直接問:
1.能夠在 HTML 模板中直接寫最簡單的 console.log(context) 來調試麼? 2.能夠在多層 DOM 樹綁定或傳遞不一樣的上下文參數麼? 3.能夠在多層 DOM 樹內層向上訪問已經生成的 Node 麼?
模板引擎團隊會給你正確的解決辦法,但一般和問題字面描述的目標有所差別。我以爲這就是你評判選擇的關鍵,你對官方給出的正確方法的承認度。
嵌入到 DOM 中
嵌入到 HTML 中
PowJS 是這麼實現的:
實現模板必需要實現的指令 預編譯輸出原生 ECMAScript 代碼 模板語法結構與 ECMAScript 函數寫法一致 最終,寫 PowJS 模板就像在寫 ECMAScript 函數。
GoHub index 中的寫法
<template>
<details func="repo" param="data" if="is.object(data.content)&&!sel(`#panel details[sha='${data.sha}']`)"
open
let="ctx=data.content"
sha="{{data.sha}}"
origin="{{ctx.Repo}}"
repo="{{data.owner}}/{{data.repo}}"
subdir="{{ctx.Subdir||''}}"
filename="{{ctx.Filename}}"
render=":ctx"
do="this.renew(sel(`#panel details[repo='${data.owner}/${data.repo}']`))"
break
>
<summary>{{ctx.Description}}</summary>
<div if="':';" each="ctx.Package,val-pkg">
<p title="{{pkg.Progress}}: {{pkg.Synopsis}}">{{pkg.Import}}</p>
</div>
</details>
<dl func="list" param="data"
if="!sel(`#panel details[sha='${data.sha}']`)&&':'||'';"
each="data.content,data.sha,val-rep"
do="this.appendTo(sel('#panel'))">
<details sha="{{sha}}" repo="{{rep.repo}}">
<summary>{{rep.synopsis}}</summary>
</details>
</dl>
</template>
複製代碼
多數模板引擎都會實現 if 、each 這些指令,上面的 PowJS 模板中還有:
全局對象 is、sel 模板(函數)命名 repo 、list 模板(函數)入口形參 data 自定義局部變量 ctx 下層模板(函數)形參推導 data.sha->sha 遍歷值到下層模板形參推導 (ctx.Package,val-pkg)->pkg 、(data.content,val-rep)->rep DOM 節點操做 this.renew、 this.appendTo,這直接渲染到頁面 DOM 樹 流程控制 break 僞節點 if="':';",渲染時根本不生成 div 節點,它是個僞節點,至關於塊代碼符號 "{}" 關鍵是整個模板結構,指令語義和 ECMAScript 函數徹底一致:
沒有數據綁定,你寫的是 ECMAScript 函數,傳參數好了,要什麼綁定 沒有事件綁定,每一個節點都是真實存在的,直接寫 addEventListener 就行了 要調試,隨便找個 do 或 if 或 let 插入 _=console.log(x), 就行了,逗號表達式幾乎能夠無縫插入全部原生語句 全部的業務邏輯都是使用者本身寫的,PowJS 只負責把他們粘合成一個函數 導出視圖是 ECMAScript 源碼,下圖截取自演示 My Folders
那麼 PowJS 是最終的選擇麼?PowJS 的理念是原生性,原生的 DOM,原生的 ECMAScript。
原生也一樣是 PowJS 的問題所在,不是全部的使用者都喜歡原生,我相信有的使用者更喜歡更抽象風格,他們眼中的原生老是帶了點 "原始"。
原生意味着你能夠擴展,引入其它 library 進行搭配,但 PowJS 永遠不會出現 define setter/getter實現的 watcher,那超出了模板引擎的範圍,若是有那必定是獨立的項目。
最後,個人觀點依然是:你的需求才是選擇模板的關鍵,適合你的纔是好的。
這裏推薦一下個人前端學習交流羣:784783012 ,裏面都是學習前端的,若是你想製做酷炫的網頁,想學習知識。本身整理了一份2018最全面前端學習資料,從最基礎的HTML+CSS+JS到移動端HTML5到各類框架都有整理,送給每一位前端小夥伴,有想學習web前端的,或是轉行,或是大學生,還有工做中想提高本身能力的,正在學習的小夥伴歡迎加入學習。
點擊:加入