跟着阮神學ES6——字符串的擴展

字符串編碼相關知識

字符的 Unicode 表示法

  1. ES6 增強了對 Unicode 的支持,容許採用\uxxxx形式表示一個字符,其中xxxx表示字符的 Unicode 碼點。javascript

  2. 限於碼點在\u0000~\uFFFF之間的字符。超出這個範圍的字符,必須用兩個雙字節的形式表示。java

  3. 直接在\u後面跟上超過0xFFFF的數值會理解錯誤。git

  4. 改進方案,只要將碼點放入大括號,就能正確解讀該字符github

    "\u{20BB7}"
    // "𠮷
    複製代碼

JavaScript中6 種表示一個字符的方法

'z' === 'z'  // true
'\z' === 'z'  // true 122
'\172' === 'z' // true 1*8*8 + 7*8 +2 = 122 // 對應下面的\ddd模式
'\x7A' === 'z' // true 7*16 + 10 // 對應下面的\xhh模式
'\u007A' === 'z' // true // Unicode 表示法
'\u{7A}' === 'z' // true // Unicode 改進後表示法
複製代碼
  1. \ddd:後面跟三位bai八進制數,該三位八進制數的值即爲對應的八進制ASCII碼值。
  2. \xhh:後面跟兩位十六進制數,該兩位十六進制數爲對應字符的十六進制ASCII碼值。

JavaScript3 種經常使用的進製表示方法

  1. 二進制0b開頭
  2. 八進制0o開頭
  3. 十六進制0x開頭

字符串的遍歷器接口

傳統的for循環沒法識別大於0xFFFF的碼點,而for...of能夠。正則表達式

for (let i of text) {
  console.log(i);
}
// "𠮷" 
複製代碼

字符串轉義

JavaScript 字符串容許直接輸入字符,以及輸入字符的轉義形式。能夠直接在字符串裏面輸入這個漢字,也能夠輸入它的轉義形式\u4e2d,二者是等價的。json

'中' === '\u4e2d' // true
複製代碼

JavaScript 規定有5個字符,不能在字符串裏面直接使用,只能使用轉義形式。數組

  • U+005C:反斜槓(reverse solidus)markdown

  • U+000D:回車(carriage return)函數

  • U+2028:行分隔符(line separator)oop

  • U+2029:段分隔符(paragraph separator)

  • U+000A:換行符(line feed)

爲消除JSON JSON.parse解析行分隔符與段分隔符的異常,ES2019 容許 JavaScript 字符串直接輸入 U+2028(行分隔符)和 U+2029(段分隔符)。

注意,模板字符串如今就容許直接輸入這兩個字符。另外,正則表達式依然不容許直接輸入這兩個字符,這是沒有問題的,由於 JSON 原本就不容許直接包含正則表達式。

JSON.stringify() 調整

JSON 數據必須是 UTF-8 編碼。可是,如今的JSON.stringify()方法有可能返回不符合 UTF-8 標準的字符串。

爲了確保返回的是合法的 UTF-8 字符,ES2019 改變了JSON.stringify()的行爲。若是遇到0xD8000xDFFF之間的單個碼點,或者不存在的配對形式,它會返回轉義字符串。

例如:僅最後的語句返回 ""𝌆""

JSON.stringify('\u{D834}') // ""\\uD834""
JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""
JSON.stringify('\uD834\uDF06') 
複製代碼

模板字符串

什麼是模板字符串?

模板字符串(template string)是加強版的字符串,用反引號(`)標識。它能夠看成普通字符串使用,也能夠用來定義多行字符串,或者在字符串中嵌入變量。

// 普通字符串
`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}?`
複製代碼

模板字符串有哪些特色?

  1. 支持字符串的換行與空格縮進。
  2. 在模板字符串中使用反引號,則前面要用反斜槓轉義。
  3. 支持使用${}嵌入變量。
  4. ${}大括號內部能夠放入任意的 JavaScript 表達式,能夠進行運算,以及引用對象屬性。
  5. ${}大括號內部還能調用函數。
  6. ${}大括號內部是一個字符串,將會原樣輸出。
  7. 模板字符串甚至還能嵌套。

模板編譯

標籤模板

什麼是標籤模板?

模板字符串能夠緊跟在一個函數名後面,該函數將被調用來處理這個模板字符串。這被稱爲「標籤模板」功能(tagged template)。標籤模板其實不是模板,而是函數調用的一種特殊形式。「標籤」指的就是函數,緊跟在後面的模板字符串就是它的參數。

標籤模板有哪些常見的應用?

  1. 過濾 HTML 字符串,防止用戶輸入惡意內容。
  2. 多語言轉換(國際化處理)。
  3. 能夠在 JavaScript 語言之中嵌入其餘語言。

模板字符串的限制

  1. 模板字符串默認會將字符串轉義(\u\x等),致使沒法嵌入其餘語言。

  2. ES2018 放鬆了對標籤模板裏面的字符串轉義的限制。若是遇到不合法的字符串轉義,就返回undefined,而不是報錯,而且從raw屬性上面能夠獲得原始字符串。

  3. 對字符串轉義的放鬆,只在標籤模板解析字符串時生效,不是標籤模板的場合,依然會報錯。

    let bad = `bad escape sequence: \unicode`; // 報錯
    複製代碼

String.fromCodePoint()

爲何須要新增String.fromCodePoint()呢?

ES5 提供String.fromCharCode()方法,用於從 Unicode 碼點返回對應字符,可是這個方法不能識別碼點大於0xFFFF的字符。ES6 提供了String.fromCodePoint()方法,能夠識別大於0xFFFF的字符,彌補了String.fromCharCode()方法的不足。

String.fromCharCode(0x20BB7)
// "ஷ"

String.fromCodePoint(0x20BB7)
// "𠮷"
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
// true
複製代碼

String.fromCodePoint()方法與下面的codePointAt()相反。

實例方法:codePointAt()

爲何須要codePointAt()?

JavaScript 內部,字符以 UTF-16 的格式儲存,每一個字符固定爲2個字節。對於那些須要4個字節儲存的字符(Unicode 碼點大於0xFFFF的字符),JavaScript 會認爲它們是兩個字符。

var s = "𠮷";
s.length // 2
s.charAt(0) // ''
s.charAt(1) // ''
s.charCodeAt(0) // 55362
s.charCodeAt(1) // 57271
複製代碼

根據以上示例發現,對於4 個字節儲存的字符,ES5提供的charAtcharCodeAt沒法返回咱們想要的值。

ES6 提供了codePointAt()方法,可以正確處理 4 個字節儲存的字符,返回一個字符的碼點。

let s = '𠮷a';

s.codePointAt(0) // 134071
s.codePointAt(1) // 57271

s.codePointAt(2) // 97
複製代碼

codePointAt()的缺陷?

可是,codePointAt()方法的參數,仍然是不正確的。上面代碼中,字符a在字符串s的正確位置序號應該是 1,可是必須向codePointAt()方法傳入 2。

怎麼規避codePointAt()的缺陷呢?

方法一:使用for...of循環

let s = '𠮷a';
for (let ch of s) {
  console.log(ch.codePointAt(0).toString(16));
}
// 20bb7
// 61
複製代碼

方法二:使用擴展運算符(...)進行展開運算

let arr = [...'𠮷a']; // arr.length === 2
arr.forEach(
  ch => console.log(ch.codePointAt(0).toString(16))
);
// 20bb7
// 61
複製代碼

怎麼判斷是否爲 4 個字節儲存的字符?

function is32Bit(c) {
  return c.codePointAt(0) > 0xFFFF;
}

is32Bit("𠮷") // true
is32Bit("a") // false
複製代碼

String.raw()

String.raw()的用途?

String.raw()方法返回一個斜槓都被轉義(即斜槓前面再加一個斜槓)的字符串,每每用於模板字符串的處理方法。

String.raw`Hi\n${2+3}!`
// 實際返回 "Hi\\n5!",顯示的是轉義後的結果 "Hi\n5!"
複製代碼

String.raw()的函數寫法

函數的形式,它的第一個參數,應該是一個具備raw屬性的對象,且raw屬性的值應該是一個數組,對應模板字符串解析後的值。

`foo${1 + 2}bar`
// 等同於
String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"
複製代碼

手寫String.raw()函數

String.raw = function (strings, ...values) {
  let output = '';
  let index;
  for (index = 0; index < values.length; index++) {
    output += strings.raw[index] + values[index];
  }

  output += strings.raw[index]
  return output;
}
複製代碼

實例方法:normalize()

normalize()方法的使用場景是什麼?

許多歐洲語言有語調符號和重音符號。爲了表示它們,Unicode 提供了兩種方法。

  1. 一種是直接提供帶重音符號的字符,好比Ǒ(\u01D1)。

  2. 另外一種是提供合成符號(combining character),即原字符與重音符號的合成,兩個字符合成一個字符,好比O(\u004F)和ˇ(\u030C)合成Ǒ(\u004F\u030C)。

這兩種表示方法,在視覺和語義上都等價,可是 JavaScript 不能識別。以下,長度不一致並且相等判斷爲false

'\u01D1'==='\u004F\u030C' //false

'\u01D1'.length // 1
'\u004F\u030C'.length // 2
複製代碼

normalize()方法如何使用?

ES6 提供字符串實例的normalize()方法,用來將字符的不一樣表示方法統一爲一樣的形式,這稱爲 Unicode 正規化。

'\u01D1'.normalize() === '\u004F\u030C'.normalize()
// true
複製代碼

normalize方法能夠接受一個參數來指定normalize的方式,參數的四個可選值以下:

  • NFC,默認參數,表示「標準等價合成」(Normalization Form Canonical Composition),返回多個簡單字符的合成字符。所謂「標準等價」指的是視覺和語義上的等價。

  • NFD,表示「標準等價分解」(Normalization Form Canonical Decomposition),即在標準等價的前提下,返回合成字符分解的多個簡單字符。

  • NFKC,表示「兼容等價合成」(Normalization Form Compatibility Composition),返回合成字符。所謂「兼容等價」指的是語義上存在等價,但視覺上不等價,好比「囍」和「喜喜」。(這只是用來舉例,normalize方法不能識別中文。)

  • NFKD,表示「兼容等價分解」(Normalization Form Compatibility Decomposition),即在兼容等價的前提下,返回合成字符分解的多個簡單字符。

    '\u004F\u030C'.normalize('NFC').length // 1
    '\u004F\u030C'.normalize('NFD').length // 2
    複製代碼

normalize()方法的限制?

normalize方法目前不能識別三個或三個以上字符的合成。這種狀況下,仍是隻能使用正則表達式,經過 Unicode 編號區間判斷。

實例方法:includes(), startsWith(), endsWith()

ES5 時代,JavaScript 只有indexOf方法,能夠用來肯定一個字符串是否包含在另外一個字符串中。

ES6 提供了三種新方法:

  • includes():返回布爾值,表示是否找到了參數字符串。

  • startsWith():返回布爾值,表示參數字符串是否在原字符串的頭部。

  • endsWith():返回布爾值,表示參數字符串是否在原字符串的尾部。

let s = 'Hello world!';

s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
複製代碼

支持第二個參數,用於限定搜索的位置

let s = 'Hello world!';

s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true 針對前n個字符,即'Hello'
s.includes('Hello', 6) // false
複製代碼

上面代碼表示,使用第二個參數n時,endsWith的行爲與其餘兩個方法有所不一樣。

endsWith針對前n個字符,而includes(), startsWith()方法針對從第n個位置直到字符串結束。

實例方法:repeat()

repeat方法返回一個新字符串,表示將原字符串重複n次。

  1. 參數若是是小數,會被取整。
  2. 若是repeat的參數是負數或者Infinity,會報錯。
  3. 若是參數是 0 到-1 之間的小數,則等同於 0,原理爲規則一。
  4. 參數NaN等同於 0。
  5. 參數是字符串,則會先轉換成數字。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
'na'.repeat(2.9) // "nana" --參數會被取整
'na'.repeat(Infinity) // RangeError --報錯
'na'.repeat(-1) // RangeError --報錯
'na'.repeat(-0.9) // ""
'na'.repeat(NaN) // ""
'na'.repeat('3') // "nanana" --先轉換成數字
複製代碼

實例方法:padStart(),padEnd()

padStart(),padEnd()怎麼使用?

ES2017 引入了字符串補全長度的功能。若是某個字符串不夠指定長度,會在頭部或尾部補全。padStart()用於頭部補全,padEnd()用於尾部補全。padStart()padEnd()一共接受兩個參數,第一個參數是字符串補全生效的最大長度,第二個參數是用來補全的字符串。

  1. 原字符串的長度,等於或大於最大長度,則字符串補全不生效,返回原字符串。
  2. 用來補全的字符串與原字符串,二者的長度之和超過了最大長度,則會截去超出位數的補全字符串。
  3. 省略第二個參數,默認使用空格補全長度。
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'

'xxx'.padStart(2, 'ab') // 'xxx' --補全不生效,返回原字符串
'xxx'.padEnd(2, 'ab') // 'xxx' --補全不生效,返回原字符串

'abc'.padStart(10, '0123456789')  // '0123456abc' --會截去超出位數的補全字符串
'x'.padStart(4)  // ' x' --默認使用空格補全長度
複製代碼

padStart(),padEnd()用途

  1. 數值補全指定位數

    '1'.padStart(10, '0') // "0000000001"
    複製代碼
  2. 提示字符串格式

    '12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
    '09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"
    複製代碼

實例方法:trimStart(),trimEnd()

ES2019 對字符串實例新增了trimStart()trimEnd()這兩個方法。它們的行爲與trim()一致,trimStart()消除字符串頭部的空格,trimEnd()消除尾部的空格。

trimStart(),trimEnd()的特徵有哪些?

  1. 返回的都是新字符串,不會修改原始字符串。
  2. 除了空格鍵,這兩個方法對字符串頭部(或尾部)的 tab 鍵、換行符等不可見的空白符號也有效。
  3. trimLeft()trimStart()的別名,trimRight()trimEnd()的別名(爲了兼容)。
相關文章
相關標籤/搜索