本文來自阮一峰-Flex 布局教程:實例篇躬翁,僅供自己學(xué)習(xí)參考整理谢床。
1. 字符串的Unicode表示法
ES6 加強了對 Unicode 的支持婆咸,允許采用\uxxxx形式表示一個字符搭独,其中xxxx表示字符的 Unicode 碼點曙聂。
"\u0061"
// "a"
但是偿枕,這種表示法只限于碼點在\u0000~\uFFFF之間的字符。超出這個范圍的字符栈暇,必須用兩個雙字節(jié)的形式表示麻裁。
"\uD842\uDFB7"
// "??"
"\u20BB7"
// " 7"
上面代碼表示,如果直接在\u后面跟上超過0xFFFF的數(shù)值(比如\u20BB7)源祈,JavaScript 會理解成\u20BB+7煎源。由于\u20BB是一個不可打印字符,所以只會顯示一個空格香缺,后面跟著一個7手销。
ES6 對這一點做出了改進,只要將碼點放入大括號赫悄,就能正確解讀該字符原献。
"\u{20BB7}"
// "??"
"\u{41}\u{42}\u{43}"
// "ABC"
let hello = 123;
hell\u{6F} // 123
'\u{1F680}' === '\uD83D\uDE80'
// true
上面代碼中馏慨,最后一個例子表明埂淮,大括號表示法與四字節(jié)的 UTF-16 編碼是等價的。
有了這種表示法之后写隶,JavaScript 共有 6 種方法可以表示一個字符倔撞。
'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true
2.字符串的遍歷接口
ES6 為字符串添加了遍歷器接口(詳見《Iterator》一章),使得字符串可以被for...of循環(huán)遍歷慕趴。
for (let codePoint of 'foo') {
console.log(codePoint)
}
// "f"
// "o"
// "o"
除了遍歷字符串痪蝇,這個遍歷器最大的優(yōu)點是可以識別大于0xFFFF的碼點鄙陡,傳統(tǒng)的for循環(huán)無法識別這樣的碼點。
let text = String.fromCodePoint(0x20BB7);
for (let i = 0; i < text.length; i++) {
console.log(text[i]);
}
// " "
// " "
for (let i of text) {
console.log(i);
}
// "??"
上面代碼中躏啰,字符串text只有一個字符趁矾,但是for循環(huán)會認(rèn)為它包含兩個字符(都不可打印)给僵,而for...of循環(huán)會正確識別出這一個字符毫捣。
3.直接輸入 U + 2028 和 U + 2029
JavaScript 字符串允許直接輸入字符,以及輸入字符的轉(zhuǎn)義形式帝际。舉例來說蔓同,“中”的 Unicode 碼點是 U+4e2d,你可以直接在字符串里面輸入這個漢字蹲诀,也可以輸入它的轉(zhuǎn)義形式\u4e2d
斑粱,兩者是等價的。
'中' === '\u4e2d' // true
但是脯爪,JavaScript 規(guī)定有5個字符则北,不能在字符串里面直接使用,只能使用轉(zhuǎn)義形式痕慢。
- U+005C:反斜杠(reverse solidus)
- U+000D:回車(carriage return)
- U+2028:行分隔符(line separator)
- U+2029:段分隔符(paragraph separator)
- U+000A:換行符(line feed)
舉例來說咒锻,字符串里面不能直接包含反斜杠,一定要轉(zhuǎn)義寫成\\
或者\u005c
守屉。
這個規(guī)定本身沒有問題惑艇,麻煩在于 JSON 格式允許字符串里面直接使用 U+2028(行分隔符)和 U+2029(段分隔符)。這樣一來拇泛,服務(wù)器輸出的 JSON 被JSON.parse
解析滨巴,就有可能直接報錯。
const json = '"\u2028"';
JSON.parse(json); // 可能報錯
JSON 格式已經(jīng)凍結(jié)(RFC 7159)俺叭,沒法修改了恭取。為了消除這個報錯,ES2019 允許 JavaScript 字符串直接輸入 U+2028(行分隔符)和 U+2029(段分隔符)熄守。
const PS = eval("'\u2029'");
根據(jù)這個提案蜈垮,上面的代碼不會報錯。
注意裕照,模板字符串現(xiàn)在就允許直接輸入這兩個字符攒发。另外,正則表達(dá)式依然不允許直接輸入這兩個字符晋南,這是沒有問題的惠猿,因為 JSON 本來就不允許直接包含正則表達(dá)式。
4.JSON.stringify() 的改造
根據(jù)標(biāo)準(zhǔn)负间,JSON 數(shù)據(jù)必須是 UTF-8 編碼偶妖。但是姜凄,現(xiàn)在的JSON.stringify()
方法有可能返回不符合 UTF-8 標(biāo)準(zhǔn)的字符串。
具體來說趾访,UTF-8 標(biāo)準(zhǔn)規(guī)定态秧,0xD800
到0xDFFF
之間的碼點,不能單獨使用扼鞋,必須配對使用屿聋。比如,\uD834\uDF06
是兩個碼點藏鹊,但是必須放在一起配對使用润讥,代表字符??
。這是為了表示碼點大于0xFFFF
的字符的一種變通方法盘寡。單獨使用\uD834
和\uDF06
這兩個碼點是不合法的楚殿,或者顛倒順序也不行,因為\uDF06\uD834
并沒有對應(yīng)的字符竿痰。
JSON.stringify()
的問題在于脆粥,它可能返回0xD800
到0xDFFF
之間的單個碼點。
JSON.stringify('\u{D834}') // "\u{D834}"
為了確保返回的是合法的 UTF-8 字符影涉,ES2019 改變了JSON.stringify()
的行為变隔。如果遇到0xD800
到0xDFFF
之間的單個碼點,或者不存在的配對形式蟹倾,它會返回轉(zhuǎn)義字符串匣缘,留給應(yīng)用自己決定下一步的處理。
JSON.stringify('\u{D834}') // ""\\uD834""
JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""
5.模板字符串
模板字符串
傳統(tǒng)的 JavaScript 語言鲜棠,輸出模板通常是這樣寫的(下面使用了 jQuery 的方法)肌厨。
$('#result').append(
'There are <b>' + basket.count + '</b> ' +
'items in your basket, ' +
'<em>' + basket.onSale +
'</em> are on sale!'
);
上面這種寫法相當(dāng)繁瑣不方便,ES6 引入了模板字符串解決這個問題豁陆。
$('#result').append(`
There are <b>${basket.count}</b> items
in your basket, <em>${basket.onSale}</em>
are on sale!
`);
模板字符串(template string)是增強版的字符串柑爸,用反引號(`)標(biāo)識。它可以當(dāng)作普通字符串使用盒音,也可以用來定義多行字符串表鳍,或者在字符串中嵌入變量。
// 普通字符串
`In JavaScript '\n' is a line-feed.`
// 多行字符串
`In JavaScript this is
not legal.`
console.log(`string text line 1
string text line 2`);
// 字符串中嵌入變量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
上面代碼中的模板字符串祥诽,都是用反引號表示譬圣。如果在模板字符串中需要使用反引號,則前面要用反斜杠轉(zhuǎn)義原押。
let greeting = `\`Yo\` World!`;
如果使用模板字符串表示多行字符串胁镐,所有的空格和縮進都會被保留在輸出之中。
$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`);
上面代碼中诸衔,所有模板字符串的空格和換行盯漂,都是被保留的,比如<ul>標(biāo)簽前面會有一個換行笨农。如果你不想要這個換行就缆,可以使用trim方法消除它。
$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`.trim());
模板字符串中嵌入變量谒亦,需要將變量名寫在${}之中竭宰。
function authorize(user, action) {
if (!user.hasPrivilege(action)) {
throw new Error(
// 傳統(tǒng)寫法為
// 'User '
// + user.name
// + ' is not authorized to do '
// + action
// + '.'
`User ${user.name} is not authorized to do ${action}.`);
}
}
大括號內(nèi)部可以放入任意的 JavaScript 表達(dá)式,可以進行運算份招,以及引用對象屬性切揭。
let x = 1;
let y = 2;
`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"
`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"
let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// "3"
模板字符串之中還能調(diào)用函數(shù)。
function fn() {
return "Hello World";
}
`foo ${fn()} bar`
// foo Hello World bar
如果大括號中的值不是字符串锁摔,將按照一般的規(guī)則轉(zhuǎn)為字符串廓旬。比如,大括號中是一個對象谐腰,將默認(rèn)調(diào)用對象的toString方法孕豹。
如果模板字符串中的變量沒有聲明,將報錯十气。
// 變量place沒有聲明
let msg = `Hello, ${place}`;
// 報錯
由于模板字符串的大括號內(nèi)部励背,就是執(zhí)行 JavaScript 代碼,因此如果大括號內(nèi)部是一個字符串砸西,將會原樣輸出叶眉。
`Hello ${'World'}`
// "Hello World"
模板字符串甚至還能嵌套。
const tmpl = addrs => `
<table>
${addrs.map(addr => `
<tr><td>${addr.first}</td></tr>
<tr><td>${addr.last}</td></tr>
`).join('')}
</table>
`;
上面代碼中芹枷,模板字符串的變量之中竟闪,又嵌入了另一個模板字符串,使用方法如下。
const data = [
{ first: '<Jane>', last: 'Bond' },
{ first: 'Lars', last: '<Croft>' },
];
console.log(tmpl(data));
// <table>
//
// <tr><td><Jane></td></tr>
// <tr><td>Bond</td></tr>
//
// <tr><td>Lars</td></tr>
// <tr><td><Croft></td></tr>
//
// </table>
如果需要引用模板字符串本身幢竹,在需要時執(zhí)行伞访,可以寫成函數(shù)。
let func = (name) => `Hello ${name}!`;
func('Jack') // "Hello Jack!"
上面代碼中理朋,模板字符串寫成了一個函數(shù)的返回值。執(zhí)行這個函數(shù)绿聘,就相當(dāng)于執(zhí)行這個模板字符串了嗽上。
6. 實例:模板編譯
下面,我們來看一個通過模板字符串熄攘,生成正式模板的實例兽愤。
let template = `
<ul>
<% for(let i=0; i < data.supplies.length; i++) { %>
<li><%= data.supplies[i] %></li>
<% } %>
</ul>
`;
上面代碼在模板字符串之中,放置了一個常規(guī)模板。該模板使用<%...%>放置 JavaScript 代碼浅萧,使用<%= ... %>輸出 JavaScript 表達(dá)式逐沙。
怎么編譯這個模板字符串呢?
一種思路是將其轉(zhuǎn)換為 JavaScript 表達(dá)式字符串洼畅。
echo('<ul>');
for(let i=0; i < data.supplies.length; i++) {
echo('<li>');
echo(data.supplies[i]);
echo('</li>');
};
echo('</ul>');
這個轉(zhuǎn)換使用正則表達(dá)式就行了吩案。
let evalExpr = /<%=(.+?)%>/g;
let expr = /<%([\s\S]+?)%>/g;
template = template
.replace(evalExpr, '`); \n echo( $1 ); \n echo(`')
.replace(expr, '`); \n $1 \n echo(`');
template = 'echo(`' + template + '`);';
然后,將template封裝在一個函數(shù)里面返回帝簇,就可以了徘郭。
let script =
`(function parse(data){
let output = "";
function echo(html){
output += html;
}
${ template }
return output;
})`;
return script;
將上面的內(nèi)容拼裝成一個模板編譯函數(shù)compile。
function compile(template){
const evalExpr = /<%=(.+?)%>/g;
const expr = /<%([\s\S]+?)%>/g;
template = template
.replace(evalExpr, '`); \n echo( $1 ); \n echo(`')
.replace(expr, '`); \n $1 \n echo(`');
template = 'echo(`' + template + '`);';
let script =
`(function parse(data){
let output = "";
function echo(html){
output += html;
}
${ template }
return output;
})`;
return script;
}
compile函數(shù)的用法如下丧肴。
let parse = eval(compile(template));
div.innerHTML = parse({ supplies: [ "broom", "mop", "cleaner" ] });
// <ul>
// <li>broom</li>
// <li>mop</li>
// <li>cleaner</li>
// </ul>