4.字符串的擴展

本文來自阮一峰-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ī)定态秧,0xD8000xDFFF之間的碼點,不能單獨使用扼鞋,必須配對使用屿聋。比如,\uD834\uDF06是兩個碼點藏鹊,但是必須放在一起配對使用润讥,代表字符??。這是為了表示碼點大于0xFFFF的字符的一種變通方法盘寡。單獨使用\uD834\uDF06這兩個碼點是不合法的楚殿,或者顛倒順序也不行,因為\uDF06\uD834并沒有對應(yīng)的字符竿痰。
JSON.stringify()的問題在于脆粥,它可能返回0xD8000xDFFF之間的單個碼點。

JSON.stringify('\u{D834}') // "\u{D834}"

為了確保返回的是合法的 UTF-8 字符影涉,ES2019 改變了JSON.stringify()的行為变隔。如果遇到0xD8000xDFFF之間的單個碼點,或者不存在的配對形式蟹倾,它會返回轉(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>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末残揉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子芋浮,更是在濱河造成了極大的恐慌抱环,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,835評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件途样,死亡現(xiàn)場離奇詭異江醇,居然都是意外死亡,警方通過查閱死者的電腦和手機何暇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,900評論 2 383
  • 文/潘曉璐 我一進店門陶夜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人裆站,你說我怎么就攤上這事条辟。” “怎么了宏胯?”我有些...
    開封第一講書人閱讀 156,481評論 0 345
  • 文/不壞的土叔 我叫張陵羽嫡,是天一觀的道長。 經(jīng)常有香客問我肩袍,道長杭棵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,303評論 1 282
  • 正文 為了忘掉前任氛赐,我火速辦了婚禮魂爪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘艰管。我一直安慰自己滓侍,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,375評論 5 384
  • 文/花漫 我一把揭開白布牲芋。 她就那樣靜靜地躺著撩笆,像睡著了一般捺球。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上夕冲,一...
    開封第一講書人閱讀 49,729評論 1 289
  • 那天氮兵,我揣著相機與錄音,去河邊找鬼耘擂。 笑死胆剧,一個胖子當(dāng)著我的面吹牛絮姆,可吹牛的內(nèi)容都是我干的醉冤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,877評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼篙悯,長吁一口氣:“原來是場噩夢啊……” “哼蚁阳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鸽照,我...
    開封第一講書人閱讀 37,633評論 0 266
  • 序言:老撾萬榮一對情侶失蹤螺捐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后矮燎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體定血,經(jīng)...
    沈念sama閱讀 44,088評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,443評論 2 326
  • 正文 我和宋清朗相戀三年诞外,在試婚紗的時候發(fā)現(xiàn)自己被綠了澜沟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,563評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡峡谊,死狀恐怖茫虽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情既们,我是刑警寧澤濒析,帶...
    沈念sama閱讀 34,251評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站啥纸,受9級特大地震影響号杏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜斯棒,卻給世界環(huán)境...
    茶點故事閱讀 39,827評論 3 312
  • 文/蒙蒙 一盾致、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧名船,春花似錦绰上、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,712評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春百揭,著一層夾襖步出監(jiān)牢的瞬間爽哎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,943評論 1 264
  • 我被黑心中介騙來泰國打工器一, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留课锌,地道東北人。 一個月前我還...
    沈念sama閱讀 46,240評論 2 360
  • 正文 我出身青樓祈秕,卻偏偏與公主長得像渺贤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子请毛,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,435評論 2 348

推薦閱讀更多精彩內(nèi)容