1. 程式人生 > >前端模板技術面面觀(2)

前端模板技術面面觀(2)

此文已由作者鄭海波授權網易雲社群釋出。

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗


Living Template Engine

String-based 和 Dom-based的模板技術都或多或少的依賴與innerHTML, 它們的區別是一個是主要是為了Rendering 一個是為了 Parsing 提取資訊

所以為什麼不結合它們兩者來完全移除對innerHTML的依賴呢?

事實上,值得慶幸的是,已經有幾個現例項子在這麼做了。

例子

  1. htmlbar: 執行在handlebar 之後的二次編譯

  2. ractivejs: 獨立

  3. Regularjs 獨立, 本文作者結精之一

基本原理


就如圖中所示,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
      }
    ]
  }
]

這個過程有以下特點

  1. 靈活強大的語法,因為它與基於字串的模板一般,DSL是自治的,完全不依賴與html,你可以想像下dom-based的模板的那些語法相關的指令,事實上它們甚至無法表達上述那段簡單的模板的邏輯。

  2. 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過程中需要的資訊(語句, 指令, 屬性...等等). 這帶來幾個好處

  1. 輕量級, 在Dom中進行讀寫操作是低效的.

  2. 可重用的.

  3. 可序列化 , 你可以在本地或伺服器端預處理這個過程。

  4. 安全, 因為安全不需要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 ♦♦ ♦♦♦
  1. 任何一類無法被另一類全面替代

  2. 它們並不是無法同時存在的,比如你可以使用字串模板來生成Dom-based的模板需要的模板字串。

參考資料

  1. Template Engines by @Sendhil

  2. string-templating-considered-harmful


免費領取驗證碼、內容安全、簡訊傳送、直播點播體驗包及雲伺服器等套餐

更多網易技術、產品、運營經驗分享請點選


相關文章:
【推薦】 AndroidTV開發(2)
【推薦】 Spring Boot + Mybatis 多資料來源配置實現讀寫分離
【推薦】 客戶端SDK測試思路