關於模板引擎的工作方式和性能?

求高人解釋模板引擎。

本人一直對模板引擎的概念比較模糊(主要是工作原理,工作流程方面),希望大家能幫忙更正。

我所理解的模板引擎是這樣的,例如velocity, jade

在用戶訪問某個Url時,伺服器將相應的模板填充好靜態數據後,轉換為html吐出,呈現給用戶。那麼用戶每次訪問,後端都需要渲染模板一次,那麼相對於直接訪問純靜態的html,使用模板引擎會不會增加服務端壓力。

問題:

1.以上我的理解是否正確?

2.我理解的模板引擎是依賴服務端的,那麼在有能力的情況下,是否採用 html+請求的方式,將渲染放在瀏覽器端更好?

3.希望大家幫忙梳理一下我的理解。


瀉藥

1、工作原理 —— 拼字元串成為代碼。

那最簡單的 JSP 舉例(因為這貨第一次運行會被編譯,比較好說事兒)

如圖。

&<%%&>內的是模板內容

&& 是頁面內容

當JSP運行被,編譯為Servlet Class後,其實做了黃字標註的操作

&<%%&> 被去掉,內部即為正常的JAVA代碼

&& 被加引號成為字元串(回車換行也被變為轉義字元)

輸出字元串內容。

2、流程 —— 訪問時,轉換模板代碼為目標源碼執行,或者第一次運行時轉為目標源碼(緩存),後續直接調用源碼運行。

所以,不管怎麼樣模板轉換與目標代碼執行肯定要消耗資源(只是消耗多點少點問題),肯定會比不用模板直接輸出 HTML 來的有壓力。

3、依賴性

從1原理可知,哪方實現的模板引擎,就依賴哪方。

在server端實現模板引擎時,依賴server端。

在客戶端實現依賴客戶端。

所以,只要在客戶端實現一種模板解析方式(引擎),用來讀取模板內容,分析並轉為客戶端可執行的程序源碼,並運行,就可以脫離服務端,在瀏覽器端渲染頁面,而不依賴服務端。

4、瀏覽器端模板 —— 依據原理,JS 可作:

剩下的無非是怎麼用正則或者其他字元串處理手段將模板字元串拼接為js源碼字元串。

並用 eval 或者 new Funciton 等手段,執行拼接後源碼。

此代碼可實時執行,或者如同jsp一般,第一次運行時執行模板-&>源碼拼接,然後將拼接後源碼緩存,之後執行模板不再進行重複拼接工作。

當然,此類優化手段很多,可以自己慢慢玩。

5、前端模板是否好

這是仁者見仁智者見智的事情,就不細說了,需要根據具體項目情況綜合考慮。

無非就是全前置,前後各半和全後置模板這幾種。

一般來說,前端模板會分擔一部分伺服器壓力,畢竟把一(小)部分計算分攤到了客戶端上。

但是可能會給(半前置)模板統一管理和更新上帶來點問題。

也會給客戶端增加(些許)壓力。

或者會造成SEO、不支持JS瀏覽器如何顯示等體驗性問題需要解決。

6、前端模板類型

這裡只根據你說的服務端模板,給出對應前端模板實現思路。

此類模板開源的有的是,GitHub 搜去吧。

實際上前端模板實現不只這(字元串拼接)一種方式,可自行拓展知識面,這裡就懶得說了 = =|||


對於伺服器端的模板引擎,都會有編譯的過程,比如smarty的話最終會編譯為php文件,用戶訪問的其實是php了,所以不會有編譯的性能問題,剩下的就是php的性能問題了,和模版沒關係

至於前端模版,好像從不關心性能問題,推薦一個前端模版,可以學慣用 https://github.com/yanhaijing/template.js


放在後端就是比較傳統的開發思路,挪到前端的話確實可能可以做一些pjax這類事,但是SEO就差多了。

前端的模板,最簡單的像nano那樣就是去做regexp解析返回拼接出來的字元串。複雜一點的一般會除了常用的模板語法解析之外還會涉及到緩存等優化。 前端模板的話與後端幾乎完全分離了,自己搭個node伺服器簡單模擬IO了,而且沒有換模板的成本。也避免了把拼接字元串的所有內容(view層的東西)與js這種controller層耦合在一起的尷尬局面。明顯比拼接字元串利於開發和維護。

以前經常用join的方式,寫起來可以保留原有的HTML代碼的縮進層次,而且效率上好於直接+拼接。現在+連接性能好很多了,而且渲染工作放到了瀏覽器中,對服務端渲染頁面減輕了一些壓力。(另一方面會依賴Ajax更多些,請求更多了)

比如我現在在寫一個單頁Web應用,那我從開發的角度看必須必須得有前端模板,不然維護不堪設想,別人也甭想和我合作了。。

有前端模板的話我是拿到json簡單一處理扔給渲染函數調用模板渲染返回的text塞進HTML代碼塊中就行了。否則結構複雜一點的JSON就會讓自己寫的拼接字元串的 函數沒法看,動輒幾個屏幕不是夢,而且這是寫在Ajax的回調函數裡面的喲~

當然把模板放在哪端還要看具體問題。如果你的動態頁面渲染比較渣而IO情況好的話可以部分放到前端,或者是像實時更新頁面的業務也比較合適。但是傳統的展示型業務就不合適了,他需要SEO,很少有時時刷新,而且動態頁面緩存就能應付很多情況了。。

比如一個電商網站的首頁,大部分是所有用戶都一樣的,並且這一天或者這兩小時內是時間無關的,那麼這部分就應在後端渲染完成並緩存,那麼直接請求就能得到緩存的公共部分了(當然現在有很多緩存策略,什麼局部緩存這樣的。總之能極大減少讀資料庫還是很值的)而像是推薦這種比較個性化的就可以放到前端,使用Ajax交互得到數據扔進模板中呈現到頁面。

無論前後端談IO性能都繞不過緩存這道坎的,而緩存和針對緩存的更新對策也都有。

總之如果沒有頁面中js的處理的話,瀏覽器得到的就是伺服器端已經查詢完渲染好的靜態文件了(但是內容是會變的所以是動態頁面,動態頁面是相對伺服器來說的)

寫了半年發現貌似跑題了呢。。。

個人一點愚見,僅供娛樂。不對的地方請啪啪。


模版引擎分兩種,用以生成html等多種文件,以html為例:

客戶端引擎,主要結合js實現html,一種以handlebar mustache為代表,實現方式為拼字元串。另一種以react為代表,實現方式為virtual Dom,

服務端引擎,以velocity這種為代表,可結合Java等語言實現,由服務端生成html返回客戶端。


1、生成靜態緩存,定期更新不就好了。

2、可以,沒什麼意義,徒增瀏覽器的工作量而已。


懶和模版引擎

拿 ejs 為例子好了,比如下面一個模版:

& &<% list.forEach(item =&> {) %&>
&&<%= item %&>& &<% } %&>
&

然後把下面這個餵給它

{
list: [1, 2, 3]
}

可以得到如下結果

& &1& &2& &3& &

如果沒有模版引擎就不得不這樣編寫 render 函數來處理列表:

function render(data){
var html = "";
data.list.forEach(item =&> {
html += `&${item}&`
});

return `&${html}&`;
}

雖然後者性能肯定比前者高得多(因為多出很多步驟),但是總不能次次自己編寫函數來生成結果把,就很麻煩,完全沒有編程體驗可談。

模版引擎(比如 ejs, art-template 這樣的)應運而生,用於快速 `填充` 數據到網頁裡面,用起來真是爽,畢竟大家都很懶嘛 2333

工作流程

就是喂數據,吐結果,只要模版確定了,喂進去數據就可以得到結果。 按照渲染的位置可以分成前端渲染和後端渲染,至於兩者區別其他人已經回答的不錯了。


我曾學習並實現過一個模版引擎 tplser,做了些工作,學到了些知識。

利用 eval 實現模版引擎

eval 接受一個字元串,字元串會被作為 JavaScript 代碼被執行利用它可以完成一個模版引擎的製作:

先來觀察 ejs 的語法:

& &<% list.forEach(item =&> {) %&>
&&<%= item %&>& &<% } %&>
&

可以看到 JavaScript 代碼嵌進去了,而且用 &<% %&> 來闊住。 如果把 &<% 與前一個 %&> 相匹配的來看的話,就是說以這樣的方式來分割代碼: %&> &<% 可以很容易的得出經過下面的代碼, 變數 html 裡面的就是模版引擎的運算結果

var html = `&`;

list.forEach(item =&> {)
html += (`&${item}&`);
}

html += "&";

把上面的代碼作為字元串傳遞給 eval 就可以得到結果,大功告成。

解釋和執行一門模版語言

仔細觀察模版語言,你會發現它有作用域,有變數,也有條件和循環,厲害點的模版引擎還可以 include 別的模版,很強大。

它們可以用 eval 的方式做,也可以用將模版看成是一門語言進行解釋運行進而求出結果(像 C 語言,Java 他們這樣的),這樣就可以不局限於 JS 平台,只要能做出該模版的解釋器,可以在任何語言下解釋執行並求得結果。

打個例子把,V8就是一種解釋器,它可以解釋運行 JavaScript 代碼,而實現一個模版引擎,可以把對應的模版語法當成是一種編程語言進行解釋求值。

這裡用 ejs 不太好解釋(因為嵌入了比較多的 JS 代碼容易混淆),換一種語法或許更能揭示出模版語言是一門特殊的編程語言的本質:

現有如下模版 Q ,它跟上面給的 ejs 模版是等價的

& {{ get (item, idx) in list }}
&{{ item }}& {{ teg }}
&

如果我們能通過某種方式將 Q 轉化成下面這樣的數據:

let ast = [
{ // 表示這個數組元素是純字元串
todo: "pure-string",
val: "& "
},
{ // 這個數組元素描述的是一個等價於 for 的循環體
todo: "get",
idxName: "idx",
valName: "item",
listName: "list",
// 注意,body 自身也是一類 ast
body: [
{ // 表示這個數組元素是純字元串
todo: "pure-string",
val: "&"
},
{ // 表示這個數組元素代表著一次變數取值 變數名是 item
todo: "render",
key: "item"
},
{ // 表示這個數組元素是純字元串
todo: "pure-string",
val: "&
"
},
]
},
{ // 表示這個數組元素是純字元串
todo: "pure-string",
val: "&
"
}
]

而且我們又恰恰運行下面這個函數遍歷一下 ast:

function calc(ast, data){
let scope = data;
let html = "";
ast.forEach(node =&> {
if (node.todo === "pure-string") {
html += node.val;
} else if (node.todo === "get") {
let { idxName, valName, listName, body } = node;
let list = scope[listName];
list.forEach((item, idx) =&> {
scope[idxName] = idx;
scope[valName] = item;
html += calc(body, scope);
});
} else if (node.todo === "render"){
html += scope[node.key];
}
});

return html;
}

看一下遍歷的結果:

沒有報錯,運行起來還挺不錯的,嗯? 返回的結果不正是我們所要的模版引擎渲染的結果么??

看起來我們的的確確的讓 JS 理解了 Q 模版的想說的。

似乎一個簡陋的模版語言解釋器完成了,它支持變數、循環,實現它的難點在於:

  1. 怎麼對源碼解釋得到 AST
  2. 如何遍歷 AST
  3. 包括其他的、所有的一切與解釋器、編程語言相關的實現難點

此外,一些解釋:

  1. AST 全名是抽象語法樹,這種數據結構描述了程序的整個執行流程,代碼即數據,數據結構就是程序流程,還有就是這個數據結構總是需要一條紙帶(作用域)作為臨時數據的存儲點,遍歷它就是在解釋運行它,遍歷的結果就是代碼的整一個執行過程和結果,而且將程序編譯成二進位程序也通常離不開這個。
  2. scope 就是作用域 這裡的作用域很垃圾 (當然你可以拓展成 JS 那樣舒服的鏈式詞法作用域)
  3. render操作其實就是根據變數名在作用域內取出對應的值 並加到渲染結果後面
  4. 我沒有寫出將模版轉化成 AST 的代碼 (鹹魚水平寫的不好,我用的是正則分離雙鬍子花括弧,然後各種搞來搞去才能求得我這樣的 AST (可能有更好的AST形式),還有就是 for 循環是可以嵌套的,這個又要好好醞釀一番)

(假裝是被邀請的)


現在前端的問題最好不要脫離實際, 因為並沒有發展到一個完整成熟的階段, 很多東西可能想著美好, 實際未必能用.

後端模板渲染技術經過: 原始: asp(後來有了razor)/jsp, 世界上最好: php, OO: python (flask, django, etc) 和 ruby (ror) 這些技術, 至今仍然有很大的影響力, 主要優勢就是: 技術比較成熟, 輪子多, 填數據方便, 設置合理的 cache 和 expired date 就基本不需要考慮更新問題. 劣勢也比較明顯: 前後端一起寫, 所以會有 ejs 這種扭曲的模板; 不適合類似 twitter 這樣需要實時更新, 有大量請求的網站, 特別是單頁網站.

前端的話我感覺叫模板技術可能也不太合適, 客戶端 js 去解析模板這種看起來可能不錯, 但實際上 js 本身的更新是個問題.

另外就是 ajax/websocket/http://socket.io/RESTful 這些東西 (並不是嚴格並列的關係), 背後則是 angular/react 等所謂前端框架的興起, 也是第一次接觸會感覺不錯, 但實際上各有各的優勢, 也有各自的缺陷, 所以根據實際場景選擇很重要, 就像第一段提的, 現在沒有銀彈, 瑞士軍刀也很懸.

實際上現在很多網站都是混合的, 既有後端渲染也有題主說的那種方式, 比如知乎一個問題幾百個答案用 websocket 傳就沒有什麼必要, 但消息這種就可以在 ajax/websocket/http://socket.io 這些里選選了, 事實上是 socket.io.


第一個,並不完全正確。比較靠譜的模板引擎,都會對引擎內容做預編譯。比如如下的模板引擎

Hello &<%= username %&>

可以編譯為如下的 JavaScript 代碼

var render = function(context) {
return "Hello " + context.username ;
}

並且編譯過程是在應用啟動時,或者模板第一次渲染的時候編譯的,並且將編譯結果(就是 render 函數)緩存在內存中,這樣和你直接手寫拼接字元串的效率沒有任何區別。

所以理論上來說你並不用擔心模板引擎的性能。

當然你非要把模板渲染的過程放在客戶端,也是可行的,優缺點一目了然啊,比如放在客戶端不利於 SEO 啊,比如用戶用了比較慢的瀏覽器(早起 IE 瀏覽器,低端 Android 手機上的瀏覽器)會很卡啊,等等。


你是指Ajax來獲取那些動態內容?

可是我覺得減少請求數比伺服器增加的那點壓力會好很多啊


所謂渲染,個人認為只是服務端把模版的字元串讀出來然後替換掉要替換的內容

個人認為數據量很小的時候,小到瀏覽者不會感覺到,把數據全部輸出來在前端用js 模版引擎處理沒什麼問題,但數據量很大的時候,屬作死,看場景吧


給你看一個最簡單的曾經用於實際系統的實現代碼:

webapplib/waTemplate.h at master · pi1ot/webapplib · GitHub

webapplib/waTemplate.cpp at master · pi1ot/webapplib · GitHub

使用文檔:

http://pan.baidu.com/s/1dDIUT8l


私以為引入模板機制目的是提高開發效率和減少維護成本,無論前端還是後端,可以簡單的理解為語法糖吧,模板引擎負責解析各式各樣的語法。

自古以來,開發效率和運行效率互相排斥,所以孰輕孰重,自己依據項目實際情況進行衡量吧。


推薦閱讀:

瀏覽器根據charset判斷編碼方式的疑問?
學習 HTML+CSS 的經典著作有哪些?
女孩子做前端開發容易嗎?最多能做幾年?
應屆生如何在四個月後找到一份web前端的工作?

TAG:前端開發 | HTML | JavaScript |