再談前端HTML模板技術
在web2.0之前,寫jsp的時候雖然有es和JSTL,但是還是堅持jsp。後面在外包公司為了快速交貨,還是用了php Smart技術。
web2.0後,前端模板技術風行。
代表有如下三大類:
String-based模板技術(基於字元串的parse和compile過程)
DOM-based模板技術(基於Dom的link或compile過程)
Living template (基於字元串的parse 和 基於dom的compile過程)
String-based templating
這是一種基於字元串的模板技術,以字元串和數據為輸入,通過用正則表達式將佔位符替換為所需數據的方式,構建出完整的 HTML 字元串。
字元串模板引擎主要依賴一下這幾個dom API:createElement,appendChild,innerHTML。
在這些api中,innerHTML有最佳的可讀性與實用性,成為事實上的主要標準,雖然其他API可能在性能上更勝一籌,但原生js的字元串生成方案中,最常用的還是innerHTML。
基於字元串的模板引擎最大的功勞就是把你從大量的夾帶邏輯的字元串拼接中解放出來了,由於它的完全基於字元串的特性,它擁有一些無可替代的優勢。
It is essentially a way to address the need to populate an HTML view with data in a better way than having to write a big, ugly string concatenation expression.
- 快速的初始化時間: 很多angular的簇擁者在奚落String-based templating似乎遺漏了這一點。
- 同構性: 完全的dom-independent,即可作為用伺服器端和瀏覽器端(客官先不要急著搬phantomjs哈).
- 更強大的語法支持:因為它們都是不是自建DSL就是基於JavaScript語法,Parser的靈活性與受限於HTML的Dom-based模板技術不可同日而語
由於基於字元串的模板方法依賴於innerHTML的渲染,所以會帶來以下問題。
- 安全問題:使用innerHTML 構建 DOM具有安全隱患,用於渲染的動態數據可能存在安全漏洞,如果沒有經過特定的轉義處理,就有可能造成 XSS攻擊或者 CSRF攻擊。
因為innerHTML具有安全隱患.,例如:
,我知道像你這樣優秀的程序員不會寫出這樣的代碼,但當html片段不完全由你來控制時(比如從遠程伺服器中),這會成為一個可能引爆的炸彈。
- 性能問題:使用innerHTML 替換 DOM效率較低,即使僅替換 DOM 的一個屬性或文本內容,也必須通過innerHTML 替換整個 DOM,從而導致瀏覽器的重排和重繪。
- 開發效率問題:由於是通過正則表達式匹配後在特定函數中拼接字元串,所以容易造成重複計算,而且完全移除現有的 DOM,再重新渲染一遍,掛載在 DOM 上的事件和狀態都將不復存
- 有可能會創建出意料之外的節點:由於html的parser非常的「友好」, 以至於它接受並不規範的寫法,從而創建出意料之外的結構,而開發者得不到錯誤提示。
代表:
- mustache及其衍生handlebar等: 弱邏輯
- Dust.js: 強邏輯 (推薦)
- doT.js: 超級快
DOM-based模板技術
這是一種基於 DOM 節點的模板技術,通過innerHTML獲取初始 DOM 結構,再通過 DOM API層級從原始 DOM 屬性中提取事件、指令、表達式和過濾器等信息,編譯成 LivingDOM,從而完成數據 Model和 View 的雙向綁定。 AngularJS就是 DOM-based模板技術的代表。
Dom-based的模板技術事實上並沒有完整的parse的過程(先拋開表達式不說),如果你需要從一段字元串創建出一個view,你必然通過innerHTML來獲得初始Dom結構. 然後引擎會利用Dom API(attributes,getAttribute,firstChild… etc)層級的從這個原始Dom的屬性中提取指令、事件等信息,繼而完成數據與View的綁定,使其」活動化」。
所以Dom-based的模板技術更像是一個數據與dom之間的「鏈接」和*「改寫」*過程。
注意,dom-based的模板技術不一定要使用innerHTML,比如所有模板都是寫在入口頁面中時, 但是此時parse過程仍然是瀏覽器所為。
DOM-based模板技術比String-based模板技術更加靈活,功能也更加強大,達到了一定意義上的數據驅動。
- 是活動的: 完成compile之後,data與View仍然保持聯繫,即你可以不依賴與手動操作Dom API來更新View
- 是運行時高效的: 可以實現局部更新
- 指令等強大的附屬物幫助我們用聲明式的方式開發APP
但其存在以下問題:
- 信息冗餘:信息承載於屬性中,這個其實是不必要和冗餘的。
由於 DOM-based模板技術通過innerHTML 獲取 DOM 編譯節點,信息承載於屬性中,造成了不必要的冗餘,同時也會影響閱讀,提升開發難度。一種解決辦法就是通過讀取屬性後再進行刪除處理,諸如removeAttribute的方式移除它們,其實這個不一定必要,而且其實並無解決它們Dom強依賴的特性,還會影響性能,降低用戶體驗。
- 初始節點獲取問題:通過innerHTML獲取初始節點,沒有獨立的語法解析器或詞法解析器,與 HTML是強依賴關係。初次進入 DOM 的內容是模板,渲染需要時間,所以會造成內容閃動——FOUC(Flash of unstyled content)這個無需多說了,只怪它初次進入dom的內容並不是最終想要的內容。
- 沒有獨立的Parser,必須通過innerHTML(或首屏)獲取初始節點,即它的語法是強依賴與HTML,這也導致它有潛在的安全問題
代表:
- AngularJS: 都28000star了還需多說么
- Knockout: 在此領域內,對Web前端而言是鼻祖級的
Livingtemplate技術
Livingtemplate技術與String-based、DOM-based模板技術的最大區別是不依賴innerHTML來渲染和提取所需信息。其主要思想是:首先,結合數據綁定技術,使用成熟的詞法解析和語法解析
技術,將輸入的字元串解析成抽象語法樹AST,而不是僅僅通過簡單的正則表達式匹配特定語法,再進行字元串拼接;其次,通過對 AST進行編譯,創建具有數據動態綁定功能的 Living DOM,從而避免使用innerHTML,解決了瀏覽器的元素閃動問題,提高了應用的安全性,其原理如圖1所示。
從圖1可知,輸入的字元串通過詞法解析器Lexer,生成對應的詞法塊。詞法塊通過語法解析器 Parser,構建抽 象 語 法 樹 AST。然 後 將 AST編譯成具有動態數據綁定功能的LivingDOM,從而實現 View 和 Model的雙向綁定。
與Dom-based 模板技術利用Dom節點承載信息所不同的是,它的中間產物AST 承載了所有Compile過程中需要的信息(語句, 指令, 屬性…等等).
我們可以發現Living templating幾乎同時擁有String-based和Dom-based模板技術的優點
利用一個如字元串模板的自定義DSL來描述結構來達到了語法上的靈活性,並在Parse後承載信息(AST)。而在Compile階段,利用AST和Dom API來完成View的組裝,在組裝過程中,我們同樣可以引入Dom-based模板技術的諸如Directive等優良的種子。
living template』s 近親 —— React
React當然也可以稱之為一種模板解決方案,它同樣也巧妙規避了innerHTML,不過卻使用的是截然不同的策略:react使用一種virtual dom的技術,它也同樣基於臟檢查,不過與眾不同的是,它的臟檢查發生在view層面,即發生在virtual dom上,從而可以以較小的開銷來實現局部更新。
- 輕量級, 在Dom中進行讀寫操作是低效的.
- 可重用的.
- 可序列化, 你可以在本地或伺服器端預處理這個過程。
- 安全, 因為安全不需要innerHTML幫我們生成初始Dom
代表:
- htmlbar: 運行在handlebar之後的二次編譯
- ractivejs: 獨立
- Regularjs獨立
此文還需進一步整理,以及自定義模板引擎思考方向與工程實踐內容補充。這方面需要下的功夫還是需要蠻多的,敬請期待。
轉載請註明來源《再談前端HTML模板技術》以及參考文檔引用鏈接。本文如有侵權及不妥之處,敬請通知本人修改,此文還是初稿狀態。謝謝
參考文檔:
基於數據驅動的動態 Web模板技術設計與實現
【交流與探討】大家怎麼看前端模板技術。。。
模板引擎原理及部分實現
構建一個使用 Virtual-DOM 的前端模版引擎
前端數據模板引擎的總結
如何實現一個基於 DOM 的模板引擎
一個對前端模板技術的全面總結
推薦閱讀: