前端模板技術面面觀(2)
此文已由作者鄭海波授權網易雲社群釋出。
歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗
Living Template Engine
String-based 和 Dom-based的模板技術都或多或少的依賴與innerHTML, 它們的區別是一個是主要是為了Rendering 一個是為了 Parsing 提取資訊
所以為什麼不結合它們兩者來完全移除對innerHTML的依賴呢?
事實上,值得慶幸的是,已經有幾個現例項子在這麼做了。
例子
基本原理
就如圖中所示,parse和compile的過程分別類似於String-based 模板技術 和 Dom-based模板技術。
下面來完整講述下這兩個過程
1 . Parsing
首先我們使用一個內建DSL來解析模板字串並輸出AST。
例如,在regularjs中,下面這段簡單的模板字串
<button {{#if !isLogin}} on-click={{this.login()}} {{/if}}> {{isLogin? 'Login': 'Wellcome'}} </button>'
會被解析為以下這段資料結構
[ { "type": "element", "tag": "button", "attrs": [ { "type": "if", "test": { "type": "expression", "body": "(!_d_['isLogin'])", "constant": false, "setbody": false }, "consequent": [ [ { "type": "attribute", "name": "on-click", "value": { "type": "expression", "body": "_c_['login']()", "constant": false, "setbody": false } } ] ], "alternate": [] } ], "children": [ { "type": "expression", "body": "_d_['isLogin']?'Login':'Wellcome'", "constant": false, "setbody": false } ] } ]
這個過程有以下特點
靈活強大的語法,因為它與基於字串的模板一般,DSL是自治的,完全不依賴與html,你可以想像下dom-based的模板的那些語法相關的指令,事實上它們甚至無法表達上述那段簡單的模板的邏輯。
Living模板技術需要同時處理dsl元素 與 xml元素來實現最終檢視層的活動性,即它們是dom-aware的,而在字串模板中其實xml元素完全可以無需關心,它們被統一視為文字元素。
2 Compiler
結合特定的資料模型(在regularjs中,是一個裸資料), 模板引擎層級遊歷AST並遞迴生成Dom節點(不會涉及到innerHTML). 與此同時,指令、事件和插值等binder也同時完成了繫結,使得最終產生的Dom是與Model相維繫的,即是活動的.
事實上,Living template的compile過程相對與Dom-based的模板技術更加純粹, 因為它完全的依照AST生成,而不是在原Dom上的改寫。
以上面的模板程式碼的一個插值為例:{{isLogin? 'Login': 'Wellcome'}}。一旦regularjs的引擎遇到這段模板與代表的語法元素節點,會進入如下函式處理
// some sourcecode from regularjs walkers.expression = function(ast){ var node = document.createTextNode(""); this.$watch(ast, function(newval){ dom.text(node, "" + (newval == null? "": String(newval))); }) return node; }
正如我們所見, 歸功於$watch函式,一旦表示式發生改變,文字節點也會隨之改變,這一切其實與angularjs並無兩樣(事實上regularjs同樣也是基於髒檢查)
與Dom-based 模板技術利用Dom節點承載資訊所不同的是,它的中間產物AST 承載了所有Compile過程中需要的資訊(語句, 指令, 屬性...等等). 這帶來幾個好處
輕量級, 在Dom中進行讀寫操作是低效的.
可重用的.
可序列化 , 你可以在本地或伺服器端預處理這個過程。
安全, 因為安全不需要innerHTML幫我們生成初始Dom
如果你檢視Living Template的輸出,你會發現是這樣的
只有需要的內容被輸出了
總結Living templating
我們可以發現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上,從而可以以較小的開銷來實現區域性更新。
Example
var MyComponent = React.createClass({ render: function() { if (this.props.first) { return <div className="first"><span>A Span</span></div>; } else { return <div className="second"><p>A Paragraph</p></div>; } } });
同樣的邏輯使用regularjs描述
{{#if first}} <div className="first"><span>A Span</span></div> {{#else}} <div className="second"><p>A Paragraph</p></div>; {{/if}}
仁者見仁智者見智, 反正我傾向於使用模板來描述結構,而不是雜糅Virtual dom和js語句。你呢?
值得一提的是,由於React的特性,它兩次render之間,內部節點的替換是無法預計的(參考這裡),所以無法有效的保留資訊,所以它也有大量的關於id的placeholder存在。你可以同樣檢視react-todomvc生成的節點
一個全面的對照表
Contrast /Solutions | String-based templating | Dom-based templating | Living templating |
---|---|---|---|
例子 | Mustache,Dustjs | Angularjs, Vuejs | Regularjs 、Ractivejs、htmlbars |
語法 | ♦♦♦ | ♦♦♦ | ♦♦♦ |
活動性 | X | ♦♦♦ | ♦♦♦ |
效能 | 初始: ♦♦♦ 更新: ♦ |
初始: ♦ 更新: ♦♦♦ |
初始: ♦ 更新: ♦♦♦ |
安全性 | ♦ | ♦♦ | ♦♦♦♦♦ |
Dom 無關 | ♦♦♦♦♦ | X | ♦♦ |
SVG support(*1) | X | ♦♦ | ♦♦♦ |
任何一類無法被另一類全面替代
它們並不是無法同時存在的,比如你可以使用字串模板來生成Dom-based的模板需要的模板字串。
參考資料
免費領取驗證碼、內容安全、簡訊傳送、直播點播體驗包及雲伺服器等套餐
更多網易技術、產品、運營經驗分享請點選。
相關文章:
【推薦】 AndroidTV開發(2)
【推薦】 Spring Boot + Mybatis 多資料來源配置實現讀寫分離
【推薦】 客戶端SDK測試思路