ES6(ES2015)為 JavaScript 引入了許多新特性,其中與字串處理相關的一個新特性——模板字面量,提供了多行字串、字串模板的功能,相信很多人已經在使用了。模板字面量的基本使用很簡單,但大多數開發者還是僅僅把它當成字串拼接的語法糖來使用的,實際上它的能力比這要強大得多哦。誇張一點地說,這可能是 ES6 這麼多特性中,最容易被低估的特性了。Here is why。
基礎特性
模板字面量在 ES2015 規範中叫做 Template Literals,在規範文件更早的版本中叫 Template Strings,所以我們見過的中文文件很多也有把它寫成 模板字串 的,有時為表述方便也非正式地簡稱為 ES6 模板。
在 ES6 之前的 JavaScript,字串作為基本型別,其在程式碼中的表示方法只有將字串用引號符(單引號 ` 或 雙引號 “)包裹起來,ES6 模板字面量(下文簡稱 ES6 模板)則使用反撇號符(`)包裹作為字串表示法。
兩個反撇號之間的常規字串保持原樣,如:
`hello world` === "hello world" // --> true
`hello "world"` === `hello "world"` // --> true
`hello `world`` === "hello `world`" // --> true
``` // --> "`" // --> true
複製程式碼
換行符也只是一個字元,因此模板字面量也自然就支援多行字元 :
console.log(`TODO LIST:
* one
* two
`)
// TODO LIST:
// * one
// * two
複製程式碼
兩個反撇號之間以 ${expression}
格式包含任意 JavaScript 表示式,該 expression 表示式的值會轉換為字串,與表示式前後的字串拼接。expression 展開為字串時,使用的是 expression.toString()
函式。
const name = "Alice"
const a = 1
const b = 2
const fruits = [`apple`, `orange`, `banana`]
const now = new Date()
console.log(`Hello ${name}`)
console.log(`1 + 2 = ${a + b}`)
console.log(`INFO: ${now}`)
console.log(`Remember to bring: ${ fruits.join(`, `) }.`)
console.log(`1 < 2 ${ 1 < 2 ? `✔︎` : `✘`}`)
// Hello Alice
// 1 + 2 = 3
// INFO: Sun May 13 2018 22:28:26 GMT+0800 (中國標準時間)
// Remember to bring: apple, orange, banana.
// 1 < 2 ✔︎
複製程式碼
正因為 expression 可以是 任意 JavaScript 表示式 ,而任意一個模板字串本身也是一個表示式,所以模板中的 expression 可以巢狀另一個模板。
const fruits = [`apple`, `orange`, `banana`]
const quantities = [4, 5, 6]
console.log(`I got ${
fruits
.map((fruit, index) => `${quantities[index]} ${fruit}s`)
.join(`, `)
}`)
// I got 4 apples, 5 oranges, 6 bananas
複製程式碼
與傳統模板引擎對比
從目前的幾個示例,我們已經掌握了 ES6 模板的基礎功能,但已足夠見識到它的本領。通過它我們可以很輕易地進行程式碼重構,讓字串拼接的程式碼不再充滿亂七八糟的單引號、雙引號、+
操作符還有反斜槓 ,變得清爽很多。
於是我們很自然地想到,在實際應用中字串拼接最複雜的場景——HTML 模板上,如果採用 ES6 模板是否可以勝任呢?傳統上我們採用專門的模板引擎做這件事情,不妨將 ES6 模板與模版引擎做對比。我們選擇 lodash
的 _.template
模板引擎,這個引擎雖不像 mustache、pug 大而全,但提供的功能已足夠完備,我們就從它的幾個核心特性和場景為例,展開對比。
第 1,基本的字串插值。_.template
使用 <%= expression %>
作為模板插值分隔符,expression 的值將會按原始輸出,與 ES6 模板相同。所以在這一個特性上,ES6 模板是完全勝任的。
-
lodash
const compiled = _.template(`hello <%= user %>!`) console.log(compiled({ user: `fred` })) // hello fred 複製程式碼
-
ES6 模板
const greeting = data => `hello ${data.user}` console.log(greeting({ user: `fred` })) // hello fred 複製程式碼
第 2,字串轉義輸出,這是 HTML 模板引擎防範 XSS 的標配功能,其原理就是要將插值表示式的值中包含的 <
、>
這種可能用於 XSS 攻擊的字元轉義為 HTML Entity。要讓 lodash 輸出轉義後的插值表示式,使用 <%- expression %>
語法即可;而如果要使用 ES6 模板方案,就要靠自己實現一個單獨的函式呼叫了。在下面的示例程式碼中,就定義了簡單的 escapeHTML 函式,在模板字串中呼叫該函式對字串進行轉義。
在這個特性上,ES6 可以的確可以做到相同的效果,但代價是要自己定義轉義函式並在表示式中呼叫,使用起來不如模板引擎封裝好的介面方便。
-
lodash
const compiled = _.template(`<b><%- value %></b>`) 複製程式碼
-
ES6 模板
const entityMap = { `&`: `&`, `<`: `<`, `>`: `>`, `"`: `"`, "`": `'`, `/`: `/`, ```: ```, `=`: `=` } const escapeHTML = string => String(string).replace(/[&<>"``=/]/g, (s) => entityMap[s]); const greeting = data => `hello ${escapeHTML(data.user)}` console.log(greeting({ user: `<script>alert(0)</script>`})); // hello <script>alert(0)</script> 複製程式碼
第 3,模板內嵌 JavaScript 語句,也就是模板引擎支援通過在模板中執行 JavaScript 語句,生成 HTML。說白了其原理與世界上最好的語言 php 的 idea 是一樣的。在 lodash 模板中,使用 <% statement %>
就可以執行 JS 語句,一個最典型的使用場景是使用 for 迴圈在模版中迭代陣列內容。
但 ES6 模板中的佔位符 ${}
只支援插入表示式,所以要在其中直接執行 for 迴圈這樣的 JavaScript 語句是不行的。但是沒關係,同樣的輸出結果我們用一些簡單的技巧一樣可以搞定,例如對陣列的處理,我們只要善用陣列的 map
、 reduce
、filter
,令表示式的結果符合我們需要即可。
-
lodash
/* 使用 for 迴圈語句,迭代陣列內容並輸出 */ const compiled = _.template(`<ul><% for (var i = 0; i < users.length; i++) { %><li><%= list[i] %></li><% } %></ul>`) console.log(compiled({ users: [`fred`, `barney`] })) // <ul><li>fred</li><li>barney</li></ul> 複製程式碼
-
ES6 模板
const listRenderer = data => `<ul>${data.users.map(user => `<li>${user}</li>`).join(``)}</ul>` console.log(listRenderer({ users: [`fred`, `barney`]})) // <ul><li>fred</li><li>barney</li></ul> 複製程式碼
在以上這 3 個示例場景上,我們發現 lodash 模板能做的事,ES6 模板也都可以做到,那是不是可以拋棄模板引擎了呢?
的確,如果在開發中只是使用以上這些基本的模板引擎功能,我們可以確實可以直接使用 ES6 模板做替換,API 更輕更簡潔,還節省了額外引入一個模板庫的成本。
但如果我們使用的是 pug、artTemplate、Handlebars 這一類大而全的模板引擎,使用 ES6 模板替換就不一定明智了。尤其是這些模板引擎在伺服器端場景下的模板快取、管道輸出、模板檔案模組化管理等特性,ES6 模板本身都不具備。並且模板引擎作為獨立的庫,API 的封裝和擴充套件性都比 ES6 模板中只能插入表示式要好。
走向進階——給模板加上標籤
截至目前介紹的特性,我們可以從上面 HTML 模板字串轉義和陣列迭代輸出兩個例子的程式碼發現這樣一個事實:
要用 ES6 模板實現複雜一點的字串處理邏輯,要依賴我們寫函式來實現。
幸運的是,除了在模板的插值表示式裡想辦法呼叫各種字串轉換的函式之外,ES6 還提供了更加優雅且更容易複用的方案——帶標籤的模板字面量(tagged template literals,以下簡稱標籤模板)。
標籤模板的語法很簡單,就是在模板字串的起始反撇號前加上一個標籤。這個標籤當然不是隨便寫的,它必須是一個可呼叫的函式或方法名。加了標籤之後的模板,其輸出值的計算過程就不再是預設的處理邏輯了,我們以下面這一行程式碼為例解釋。
const message = l10n`I bought a ${brand} watch on ${date}, it cost me ${price}.`
複製程式碼
在這個例子裡,l10n
就是標籤名,反撇號之間的內容是模板內容,這一行語句將模板表示式的值賦給 message
,具體的處理過程為:
-
JS 引擎會先把模板內容用佔位符分割,把分割得到的字串存在陣列
strings
中(以下程式碼僅用來演示原理)const strings = "I bought a ${brand} watch on ${date}, it cost me ${price}".split(/${[^}]+}/) // ["I bought a ", " watch on ", ", it cost me ", "."] 複製程式碼
-
然後再將模板內容裡的佔位符表示式取出來,依次存在另一個陣列
rest
中const rest = [brand, date, price] 複製程式碼
-
執行
i18n(strings, ...rest)
函式,即呼叫l10n
,並傳入兩部分引數,第一部分是strings
作為第一個引數,第二部分是將rest
展開作為餘下引數。const message = l10n(strings, ...rest) 複製程式碼
因此,如果將以上單步分解合併在一起,就是這樣的等價形式:
const message = l10n(["I bought a ", " watch on ", ", it cost me ", "."], brand, date, price)
複製程式碼
也就是說當我們給模板前面加上 l10n
這個標籤時,實際上是在呼叫 l10n
函式,並以上面這種方式傳入呼叫引數。 l10n
函式可以交給我們自定義,從而讓上面這一行程式碼?輸出我們想要的字串。例如我們如果想讓其中的日期和價格用本地字串顯示,就可以這樣實現:
const l10n = (strings, ...rest) => {
return strings.reduce((total, current, idx) => {
const arg = rest[idx]
let insertValue = ``
if (typeof arg === `number`) {
insertValue = `¥${arg}`
} else if (arg instanceof Date) {
insertValue = arg.toLocaleDateString(`zh-CN`)
} else if (arg !== undefined) {
insertValue = arg
}
return total + current + insertValue
}, ``);
}
const brand = `G-Shock`
const date = new Date()
const price = 1000
l10n`I bought a ${brand} watch on ${date}, it cost me ${price}.`
// I bought a G-Shock watch on 2018/5/16, it cost me ¥1000.
複製程式碼
這裡的 l10n
就是個簡陋的傻瓜式的本地化模板標籤,它支援把模板內容裡的數字當成金額加上人民幣符號,把日期轉換為 zh-CN
地區格式的 2018/5/16
字串。
乍一看沒什麼大不了的,但設想一下相同的效果用沒有標籤的模板要怎樣實現呢?我們需要在模板之內的日期表示式和價格數字表示式上呼叫相應的轉換函式,即 ${date.toLocaleDateString(`zh-CN`)}
和 ${ `¥` + price}
。一次呼叫差別不大,兩次、三次呼叫的情況下,帶標籤的模板明顯勝出。不僅符合 DRY(Don`t Repeat Yourself)原則,也可以讓模板程式碼更加簡潔易讀。
超越模板字串
帶標籤的模板字面量建立在非常簡單的原理上——通過自己的函式,自定義模板的輸出值。
tag`template literals`
複製程式碼
而 ES6 規範沒有對這裡可以使用的 tag
函式做任何限制,意味著 任何函式 都可以作為標籤加到模板前面,也就意味著這個函式:
- 可以是立即返回的同步函式,也可以是非同步的
async
函式(支援函式內await
非同步語句並返回 Promise) - 可以是返回字串的函式,也可以是返回數字、陣列、物件等任何值的函式
- 可以是純函式,也可以是有副作用的非純函式
所以只要你願意,A:你可以把 任意 JavaScript 語句 放到標籤函式裡面去;B:你可以讓標籤函式返回 任意值 作為模板輸出。有了如此強大的擴充套件能力,也難怪一開始 ES6 標準中對模板規範的命名是 Template Strings,後來正式命名卻改成了 Template Literals,因為它的能力已經超越了模板字串,去追求詩和遠方了。
當然在實際的使用中還是應該保持理智,不能手裡拿著錘子看什麼都像釘子。而以下這 5 種應用場景,倒是可以算是真正的釘子。
用例 1:使用 String.raw
保留原始字串
String.raw
是 ES2015 規範新增的 String
物件的靜態成員方法,但通常並不作為函式直接呼叫,而是作為語言標準自帶的模板字面量標籤使用,用來保留模板內容的原始值。
其作用與 Python 語言中,字串表示式的引號前加 r
字首效果類似。JavaScript 需要這樣的特性,只是剛好用 String.raw
實現了它。
var a = String.raw`
`
var b === `
`
// a === b -> false
// a.length === 4 -> true
// b.length === 2 -> true
複製程式碼
String.raw
標籤在某些場景下可以幫我們節省很多繁瑣的工作。其中一個典型的場景就是,實際開發中我們常遇到在不同程式語言之間通過 JSON 文字傳輸協議資料的情況,如果遇到包含轉義字元和 "
(JSON 屬性需用引號)的文字內容,很容易因細節處理不當導致 JSON 解析出錯。
而我本人遇到過這樣一個真實案例,就是由 Android 終端向 JavaScript 傳輸 JSON 資料時,有一條資料中使用者的暱稱包含了 "
字元。JavaScript 收到的 JSON 字串文字為:
{"nickname":"槍鍋&[{鍋}]"鍋":鍋","foo":"bar"}
複製程式碼
但這裡如果直接將內容用引號賦值給 JSON.parse(`{"nickname":"槍鍋&[{鍋}]"鍋":鍋","foo":"bar"}`)
就會遇到 SyntaxError: Unexpected token 鍋 in JSON at position 22
的錯誤,因為單引號中的 被作為轉義字元,未保留在
input
的值中。要得到正確的 JSON 物件,使用 String.raw 處理即可:
JSON.parse(String.raw`{"nickname":"槍鍋&[{鍋}]"鍋":鍋","foo":"bar"}`)
// { nickname: `槍鍋&[{鍋}]"鍋":鍋`, foo: `bar` }
複製程式碼
用例 2:從標籤模板到 HTML 字串
前面講到 ES6 模板與模板引擎的對比時,提到模板引擎通過手動 escapeHTML 模板轉義不安全的字元的問題。現在我們瞭解了標籤模板之後,可以將外部定義的 escapeHTML 邏輯直接放到標籤函式中,這樣就不需要在模板中每一個插入表示式前,都呼叫 escapeHTML 函式了。
const safeHTML = (strings, ...rest) => {
const entityMap = {
`&`: `&`,
`<`: `<`,
`>`: `>`,
`"`: `"`,
"`": `'`,
`/`: `/`,
```: ```,
`=`: `=`
}
const escapeHTML = string => String(string).replace(/[&<>"``=/]/g, (s) => entityMap[s]);
return strings.reduce((total, current, idx) => {
const value = rest[idx] || ``
return total + current + escapeHTML(value)
}, ``);
}
const evilText = `<script>alert(document.cookie)</script>`
safeHTML`${evilText}`
// "<script>alert(document.cookie)</script>"
複製程式碼
我們這裡實現的 safeHTML 作為 demo 用,並不保證生產環境完備。
你一定想到了,像 HTML 模板這樣的常用模板標籤一定有現成的 npm 庫了。沒錯, common-tags
庫就是我們想要的這個庫了。common-tags
是一個小而精的模板標籤庫,被包括 Angular, Slack, Ember 等在內的很多大型專案所依賴。它包含了十幾個常用的用於字串模板處理的函式,例如 html
, safeHTml
等,讓我們可以偷懶少造一些輪子。
const safeHtml = require(`common-tags/lib/safeHtml`)
const evilText = `<script>alert(document.cookie)</script>`
safeHtml`<div>${evilText}</div>`
複製程式碼
common-tags
也提供了擴充套件 API,生成可以讓我們更輕鬆地實現自定義的標籤。
用例 3:從模板標籤到 DOM 元素
common-tags
庫裡包含的 html
和 safeHtm
模板標籤,依然是返回字串的函式。這意味著如果我們將它用在瀏覽器端得到了模板值 output
字串,依然要進一步使用 element.innerHTML = output
才可以將模板內容渲染到 DOM 元素,顯示到介面。
不過既然模板字面量的標籤函式可以返回任意值,我們不妨直接再進一步將 element.innerHTML = output
語句也放到標籤函式中,並將 element 作為返回值。這樣一來我們就可以直接用模板字面量的值作為真實的 DOM Element ,並且直接用 DOM API 操作其返回值了。
不出意外,這個想法當然也有人已想到了,@choojs/nanohtml
就是做這件事的。choojs
是一個還很年輕的前端框架(又來),不過我們今天不討論它,只看它所包含的 nanohtml
。nanohtml 的基本思路可以用這樣的 Hello World 來演示:
var nanohtml = require(`nanohtml`)
var element = nanohtml`<div>Hello World!</div>`
document.body.appendChild(element)
複製程式碼
除了像程式碼所示返回 DOM Element,nanohtml 也支援在模板中的插值表示式中插入任意的標準 DOM Element,插入的元素會根據模板 markup 渲染,作為返回值 element 的子元素。
var nanohtml = require(`nanohtml`)
var h3 = document.createElement(`h3`)
h3.textContent = `Hello World`
var element = nanohtml`<div>${h3}</div>`
document.body.appendChild(element)
複製程式碼
用例 4:非同步操作
結合 ES2017 的 async
await
特性,我們還可以定義一個 async 型別的標籤函式,並在函式中呼叫非同步操作以達到。
例如上面實現的傻瓜式 l10n
標籤,我們可以讓它更傻瓜一些,把模板最後拼接出的英文句子,翻譯為本地語言之後再輸出。而翻譯的過程,就可以通過非同步呼叫第三方的翻譯 API 例如 Google Translate API 等來實現。於是我們就有這樣的虛擬碼:
const l10n = async (strings, ...rest) => {
return strings.reduce((total, current, idx) => {
const arg = rest[idx]
let insertValue = ``
if (typeof arg === `number`) {
insertValue = `¥${arg}`
} else if (arg instanceof Date) {
insertValue = arg.toLocaleDateString(`zh-CN`)
} else if (arg !== undefined) {
insertValue = arg
}
const line = total + current + insertValue
const language = navigator.language // "zh-CN"
// 假裝 TRANSLATE_SERVICE 是一個可用的翻譯服務
const translated = await TRANSLATE_SERVICE.translate(line, { language })
return translated
}, ``)
}
l10n`I bought a ${`G-Shock`} watch on ${new Date()}, it cost me ${1000}`.then(console.log)
// "我在 2018/5/16 買了一個 G-Shock 手錶,花了我 1000 元"
複製程式碼
當然在不支援 async、await 也不支援 generator 生成器的環境下,使用 Promise 封裝非同步呼叫,使標籤函式返回 Promise 例項,也可以實現非同步操作。
const tag = (strings, ...rest) => {
return new Promise((resolve) => {
setTimeout(resolve, 1000, `Hello World`)
})
}
tag`Anything`.then(console.log)
// 1s 後 console 將輸出:
// "Hello World"
複製程式碼
用例 5:DSL
DSL(領域專用語言)是一個挺唬人的概念,顧名思義所謂 DSL 是指未解決某一特定領域的問題推出的語言。DSL 有按某種條件約束的規則——語法,故稱得上是語言。通常 DSL 用於程式設計領域,通過計算機程式處理 DSL,有些功能強大的 DSL 語言也會被認為是 mini 程式語言。
典型的 DSL 有:
- 正規表示式
- 資料庫查詢語言(SQL)
- CSS Selector 的規則也是一種 DSL
- 文字處理的瑞士軍刀 awk,sed 因為其複雜的使用規則也被認為是 DSL
合理使用 ES6 標籤模板,可以讓 JavaScript 對內嵌 DSL 的處理更加簡潔。嚴格來說,我們上面用例 2 和用例 3 對中的 html
類别範本標籤,就算是 DSL 的範疇了。下面是一些典型的案例:
注意:以下模板標籤僅作描述,需要自行實現
案例 1:DOM 選擇器
例如我們可以用標籤實現一個 DOM 選擇器,形如:
var elements = query`.${className}` // the new way
複製程式碼
雖然第一眼看上去只是另一個 jQuery 選擇器,但這種呼叫方式天然支援我們在模板之中嵌入任意的插值表示式,在 API 的 expressive 這個特性上有提升。而且 query 函式由我們自由實現,意味著可以自由地在 query 函式中加入 selector 值規範化校驗,返回值規範化等額外功能。
案例 2:Shell 指令碼
在 Node.js 環境下,我們還可以實現一個 sh
標籤用於描述 shell 指令碼的呼叫:
var proc = sh`ps aux | grep ${pid}`
複製程式碼
看到這行語句我們下意識地會想到,這裡的 API 會呼叫 shell 執行模板拼接出來的命令,而 proc 應該是該命令執行的結果或輸出。
案例 3:正規表示式構造器
以 re
標籤實現一個動態的正規表示式構造器,這種執行時生成的正規表示式,通常要自己定義函式,並呼叫 new RegExp 實現。
var match = input.match(re`d+${separator}d+`)
複製程式碼
案例 4:國際化與本地化
可以實現這樣一個 i18n
和 l10n
模板標籤:約定在模板字串中的插值表示式 ${expression}
後,以 :type
格式指定 expression 表示式期望的文字顯示格式,實現自定義的模板輸出功能。
var message = l10n`Hello ${name}; you are visitor number ${visitor}:n!
You have ${money}:c in your account!`;
複製程式碼
這裡的 l10n
標籤與我們在上文 hard-code 的版本相比,增加了 :type
識別符號以表示類別,例如 :n
表示數字,:c
表示貨幣。這些型別識別符號的規則可以在 l10n
的實現程式碼中約定。而這個約定的意味就有點自行定義 DSL 的味道了。
以上 4 個 case 的共同點是,我們首先約定了有相似模式的 API 介面,它們都表現為帶標籤的模板的形式——一個模板名後跟模板內容。
雖然我們作為實現者知道,實際上在呼叫標籤模板時,本質上是將模板內容重組為 (strings, ...rest)
形式再傳給標籤函式呼叫的。但這樣的 API 呼叫看上去卻很像是隻有一個函式和一個引數,讓人一眼看到就能猜出來 API 的用途。
好的 API 應當有良好的自我描述性,將複雜的實現細節封裝起來,並且儘量專注做好一件事。從這個角度來說帶標籤的 ES6 模板非常適合處理 JS 內嵌的 DSL,甚至可以幫助我們在特定的業務邏輯中實現一個 mini DSL。
更多探索
以上就是對 ES6 模板語法和實用價值的介紹。講到實踐,得益於其原理的簡潔,我們可以立即享受到它帶來的好處。在 Node.js 環境下,毫無疑問我們可以立即使用不用遲疑;在瀏覽器環境下,使用我們的老朋友 Babel 就可以將其轉換為相容的 ES5 程式碼。
總結起來,ES6 模板中最激動人心的特性還是標籤,小小的標籤用簡單的原理提供了異常豐富的擴充套件能力,頗有點四兩撥千金的感覺。基於它,JavaScript 社群已經產生了很多新的想法併產生了很多實實在在的工具庫。
除了我們在前面示例中有簡單提到 common-tags
和 nano-html
兩個庫,也有很多實現了特定領域功能的標籤庫,例如 SQL 相關的、國際化和本地化相關的,用 tagged template literals 這幾個關鍵詞在 npm 搜尋就可以找到別人造的輪子。
但相比其他主題,社群關注量最大的探索還是集中在將模板字面量與 HTML 模板的結合上,有 3 個代表性的框架致力於採用 template literals 的方案並結合其他 good stuff 實現可以媲美 Virtual DOM 的快速渲染方案。
- hyperHTML 旨在成為 Virtual DOM 替代品的倉庫,在官方文件中明確直出,其核心理念就是採用 ES6 模板 作為 Virtual DOM Alternative
- lit-html Google Polymer 團隊推出的庫,理念與 hyperHTML 類似,結合了 ES6 模板和 HTML
<template>
元素的優點,實現 DOM 元素的快速渲染和更新 - choojs 一個 minify+gzip 後只有 4KB 的現代前端開發框架,與 hyperHTML 和 lit-html 不同,是一個更加全功能的框架,目前(2018 年 5 月)Star 數比前兩者都多
這 3 個框架的共同點是都採用了 tagged template literals,並且放棄使用 Virtual DOM 這種現在非常火爆的概念,但號稱一樣能實現快速渲染和更新真實 DOM。從各自提供的資料上看,也的確都有著不俗的表現。礙於篇幅在這裡我們就不再展開討論了。
這些框架也反映出 ES6 模板的確潛力很大,更深入的就留給我們未來探索,以下這些文章都是不錯的參考。
- 模板字串 – MDN 文件
- What can you do with ES6 string template literals? – dherman/template-literals.md
- Template strings: embedded DSLs in ECMAScript 6
- ES6 Template Literals, the Handlebars killer?
- lit-HTML 在 Chrome Dev Summit 2017 上的演示
- Efficient, Expressivelit-HTML 在 Polymer Summit 2017 上的演示
- hyperHTML 與 lit-html 對比
- hyperHTML 與 Virtual DOM 對比
- Choo 框架架構與效能介紹
本文同步發表在 SegmentFault 和 個人部落格