詳解JavaScript ES6中的模板字符串
在 ES6 中引入了一種新的字符串字面量 — 模板字符串,除了使用反引號 (`) 表示,它們看上去和普通的字符串沒有什么區(qū)別。在最簡單的情況下,他們就是普通的字符串:
context.fillText(`Ceci n'est pas une cha?ne.`, x, y); context.fillText(`Ceci n'est pas une cha?ne.`, x, y);
之所以被稱為模板字符串,是因為模板字符串為 JS 引入了簡單的字符串插值特性,也就是說,可以方便優(yōu)雅地將 JS 的值插入到字符串中。
很多地方可以用到模板字符串,看下面這個不起眼的錯誤提示消息:
function authorize(user, action) { if (!user.hasPrivilege(action)) { throw new Error( `User ${user.name} is not authorized to do ${action}.`); } } function authorize(user, action) { if (!user.hasPrivilege(action)) { throw new Error( `User ${user.name} is not authorized to do ${action}.`); } }
上面代碼中,${user.name} 和 ${action} 被稱為模板占位符,JavaScript 將把 user.name和 action 的值分別插到對應(yīng)的位置上,然后生成像這樣 “User jorendorff is not authorized to do hockey.” 的字符串。
現(xiàn)在,我們看到了一個比 + 運算符更優(yōu)雅的語法,下面是一些你期待的特性:
模板占位符可以是任何 JavaScript 表達式,所以函數(shù)調(diào)用和四則運算等都是合法的。(甚至你還可以在一個模板字符串中嵌套另一個模板字符串。)
如果一個值不是字符串,它將被轉(zhuǎn)換為字符串。例如,如果 action 是一個對象,那么該對象的 .toString() 將被調(diào)用,來將其轉(zhuǎn)換為字符串。
如果你想在模板字符串中使用反引號,你需要使用反斜杠 \ 將其轉(zhuǎn)義。
同樣地,如果想在模板字符串中輸出 ${,也需要使用反斜杠將其轉(zhuǎn)義:\${ 或 $\{。
模板字符串可以跨越多行:
$("#warning").html(` <h1>Watch out!</h1> <p>Unauthorized hockeying can result in penalties of up to ${maxPenalty} minutes.</p> `); $("#warning").html(` <h1>Watch out!</h1> <p>Unauthorized hockeying can result in penalties of up to ${maxPenalty} minutes.</p> `);
模板字符串中所有的空格、換行和縮進,都將被原樣輸出到結(jié)果字符串中。
下面我們來看看模板字符串做不到的事情:
不會自動轉(zhuǎn)義特殊字符,為了避免跨站腳本漏洞,你還是需要小心對待不可信的數(shù)據(jù),這一點上與普通字符串一樣。
不能與國際化庫配合使用,不處理特殊語言格式的數(shù)字、日期等。
不是模板引擎(比如 Mustache 或 Nunjucks)的替代品。模板字符串沒有處理循環(huán)的語法 — 不能通過一個數(shù)組構(gòu)建出一個表格(table)。
為了解決這些限制,ES6 為開發(fā)者和庫設(shè)計者提供了另一種模板字符串 — 標簽?zāi)0濉?/p>
標簽?zāi)0宓恼Z法很簡單,只需要在開始的反引號前引入一個標簽??吹谝粋€例子:SaferHTML,我們要使用這個標簽?zāi)0鍋斫鉀Q上述的第一個限制:自動轉(zhuǎn)義特殊字符。
需要注意的是,SaferHTML 方法并不是 ES6 標準庫提供的,我們需要自己來實現(xiàn):
var message = SaferHTML`<p>${bonk.sender} has sent you a bonk.</p>`; var message = SaferHTML`<p>${bonk.sender} has sent you a bonk.</p>`;
這里的 SaferHTML 標簽是單個標識符,標簽也可以是屬性,比如 SaferHTML.escape,甚至還可以是方法調(diào)用:SaferHTML.escape({unicodeControlCharacters: false})。準確地說,任何 ES6 的成員表達式或調(diào)用表達式都可以作為標簽。
可以看出,模板字符串僅僅是字符串連接的語法糖,而標簽?zāi)0宕_是一個完全不同的東西:函數(shù)調(diào)用。
所以,上面代碼等價于:
var message = SaferHTML(templateData, bonk.sender); var message = SaferHTML(templateData, bonk.sender);
其中 templateData 是一個不可變的字符串數(shù)組,由 JS 引擎基于源模板字符串生成,這里的數(shù)組含有兩個元素,因為模板字符串被占位符分隔后含有兩個字符串,因此,templateData 將是這樣: Object.freeze(["<p>", " has sent you a bonk.</p>"]
(事實上,templateData 上還有另一個屬性:templateData.raw,本文并深入不討論該屬性。該屬性的值也是一個數(shù)組,包含了標簽?zāi)0逯兴械淖址糠?,但字符串中包含了轉(zhuǎn)義序列,看上去更像源代碼中的字符串,比如 \n。ES6 的內(nèi)置標簽 String.raw 將使用這些字符串。)
這就使得 SaferHTML 方法可以隨意解析這兩個字符串,存在 N 中替換方式。
在繼續(xù)閱讀錢,你可能在苦苦思索如何實現(xiàn) SaferHTML 方法。
下面是一種實現(xiàn)(gist):
function SaferHTML(templateData) { var s = templateData[0]; for (var i = 1; i < arguments.length; i++) { var arg = String(arguments[i]); // Escape special characters in the substitution. s += arg.replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">"); // Don't escape special characters in the template. s += templateData[i]; } return s; } function SaferHTML(templateData) { var s = templateData[0]; for (var i = 1; i < arguments.length; i++) { var arg = String(arguments[i]); // Escape special characters in the substitution. s += arg.replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">"); // Don't escape special characters in the template. s += templateData[i]; } return s; }
有了上面的方法,即使使用一個惡意的用戶名,用戶也是安全的。
一個簡單的例子并不足以說明標簽?zāi)0宓撵`活性,讓我們重溫一下上面列舉的模板字符串的限制,看看我們還可以做些什么。
模板字符串不會自動轉(zhuǎn)義特殊字符,但是我們可以通過標簽?zāi)0鍋斫鉀Q這個問題,事實上我們還可以將 SaferHTML 這個方法寫的更好。從安全角度來看,這個 SaferHTML 非常脆弱。在 HTML 中,不同的地方需要用不同的方式去轉(zhuǎn)義,SaferHTML 并沒有做到。稍加思考,我們就可以實現(xiàn)一個更加靈活的 SaferHTML方法,能夠?qū)?templateData 中的任何一個 HTML 轉(zhuǎn)義,知道哪個占位符是純 HTML;哪個是元素的屬性,從而需要對 ' 和 " 轉(zhuǎn)義;哪個是 URL 的 query 字符串,從而需要用 URL 的 escaping 方法,而不是 HTML 的 escaping;等等。這似乎有些牽強,因為 HTML 轉(zhuǎn)義效率比較低。辛運是的,標簽?zāi)0宓淖址潜3植蛔兊?,SaferHTML 可以緩存已經(jīng)轉(zhuǎn)義過的字符串,從而提高效率。
模板字符串并沒有內(nèi)置的國際化特性,但通過標簽?zāi)0澹覀兛梢蕴砑釉撎匦浴?a target="_blank" >Jack Hsu 的文章詳細介紹了實現(xiàn)過程,看下面例子:
i18n`Hello ${name}, you have ${amount}:c(CAD) in your bank account.` // => Hallo Bob, Sie haben 1.234,56 $CA auf Ihrem Bankkonto. i18n`Hello ${name}, you have ${amount}:c(CAD) in your bank account.` // => Hallo Bob, Sie haben 1.234,56 $CA auf Ihrem Bankkonto.
上面例子中的 name 和 amount 很好理解,將被 JS 引擎替換為對應(yīng)的字符串,但是還有一個沒有見過的占位符::c(CAD),這將被 i18n 標簽處理,從 i18n 的文檔可知::c(CAD)表示 amount 是加拿大美元貨幣值。
模板字符串不能替代 Mustache 和 Nunjucks 這類模板引擎,部分原因在于模板字符串不支持循環(huán)和條件語句。我們可以編寫一個標簽來實現(xiàn)這類功能:
// Purely hypothetical template language based on // ES6 tagged templates. var libraryHtml = hashTemplate` <ul> #for book in ${myBooks} <li><i>#{book.title}</i> by #{book.author}</li> #end </ul> `; // Purely hypothetical template language based on // ES6 tagged templates. var libraryHtml = hashTemplate` <ul> #for book in ${myBooks} <li><i>#{book.title}</i> by #{book.author}</li> #end </ul> `;
靈活性還不止于此,需要注意的是,標簽函數(shù)的參數(shù)不會自動轉(zhuǎn)換為字符串,參數(shù)可以是任何類型,返回值也一樣。標簽?zāi)0迳踔量梢圆恍枰址憧梢允褂米远x標簽來創(chuàng)建正則表達式、DOM 樹、圖片、代表整個異步進程的 Promise、JS 數(shù)據(jù)結(jié)構(gòu)、GL 著色器…
標簽?zāi)0逶试S庫設(shè)計者創(chuàng)建強大的領(lǐng)域特定語言。這些語言可能看上去并不像 JS,但他們可以無縫嵌入到 JS 中,并且可以與語言的其余部分進行交互。順便說一下,我還沒有在其他語言中見過類似的特性,我不知道這個特性講給我們帶來些什么,但各種可能性還是非常令人興奮的。
相關(guān)文章
js對象屬性的攔截與Proxy代理與Reflect映射的用法和區(qū)別講解
reflect和proxy都是JavaScript中用于處理對象的特殊API,下面這篇文章主要給大家介紹了關(guān)于js對象屬性的攔截與Proxy代理與Reflect映射的用法和區(qū)別,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2023-06-06JavaScript高級程序設(shè)計(第3版)學(xué)習(xí)筆記2 js基礎(chǔ)語法
這一篇復(fù)習(xí)一下ECMAScript規(guī)范中的基礎(chǔ)語法,英文好的朋友可以直接閱讀官方文檔。JavaScript本質(zhì)上也是一種類C語言,熟悉C語言的朋友,可以非常輕松的閱讀這篇文章,甚至都可以跳過,不過建議你最好還是看一看,在介紹的同時,我可能會引用一些自認為不易理解且比較流行的用法。2012-10-10

javascript創(chuàng)建數(shù)組之聯(lián)合數(shù)組的使用方法示例