ES6語法總結(3)--字符串的擴展(1)

字符串的擴展

  1. 字符的 Unicode 表示法
  2. codePointAt()
  3. String.fromCodePoint())
  4. 字符串的遍歷器接口
  5. normalize()
  6. includes(), startsWith(), endsWith()
  7. repeat()
  8. padStart(),padEnd()
  9. matchAll())

ES6 增強了對 Unicode 的支持,而且擴展了字符串對象。javascript

1.字符的 Unicode 表示法

JavaScript 容許採用\uxxxx形式表示一個字符,其中xxxx表示字符的 Unicode 碼點。java

"\u0061"
// "a"

可是,這種表示法只限於碼點在\u0000~\uFFFF之間的字符。超出這個範圍的字符,必須用兩個雙字節的形式表示。web

"\uD842\uDFB7"
// "𠮷"

"\u20BB7"
// " 7"

上面代碼表示,若是直接在\u後面跟上超過0xFFFF的數值(好比\u20BB7),JavaScript 會理解成\u20BB+7。因爲\u20BB是一個不可打印字符,因此只會顯示一個空格,後面跟着一個7正則表達式

ES6 對這一點作出了改進,只要將碼點放入大括號,就能正確解讀該字符。svg

"\u{20BB7}"
// "𠮷"

"\u{41}\u{42}\u{43}"
// "ABC"

let hello = 123;
hell\u{6F} // 123

'\u{1F680}' === '\uD83D\uDE80'
// true

上面代碼中,最後一個例子代表,大括號表示法與四字節的 UTF-16 編碼是等價的。測試

有了這種表示法以後,JavaScript 共有 6 種方法能夠表示一個字符。編碼

'\z' === 'z'  // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true

2.codePointAt()

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

var s = "𠮷";

s.length // 2
s.charAt(0) // ''
s.charAt(1) // ''
s.charCodeAt(0) // 55362
s.charCodeAt(1) // 57271

上面代碼中,漢字「𠮷」(注意,這個字不是「吉祥」的「吉」)的碼點是0x20BB7,UTF-16 編碼爲0xD842 0xDFB7(十進制爲55362 57271),須要4個字節儲存。對於這種4個字節的字符,JavaScript 不能正確處理,字符串長度會誤判爲2,並且charAt方法沒法讀取整個字符,charCodeAt方法只能分別返回前兩個字節和後兩個字節的值。code

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

let s = '𠮷a';

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

s.codePointAt(2) // 97

codePointAt方法的參數,是字符在字符串中的位置(從 0 開始)。上面代碼中,JavaScript 將「𠮷a」視爲三個字符,codePointAt 方法在第一個字符上,正確地識別了「𠮷」,返回了它的十進制碼點 134071(即十六進制的20BB7)。在第二個字符(即「𠮷」的後兩個字節)和第三個字符「a」上,codePointAt方法的結果與charCodeAt方法相同。

總之,codePointAt方法會正確返回 32 位的 UTF-16 字符的碼點。對於那些兩個字節儲存的常規字符,它的返回結果與charCodeAt方法相同。

codePointAt方法返回的是碼點的十進制值,若是想要十六進制的值,能夠使用toString方法轉換一下。

let s = '𠮷a';

s.codePointAt(0).toString(16) // "20bb7"
s.codePointAt(2).toString(16) // "61"

你可能注意到了,codePointAt方法的參數,仍然是不正確的。好比,上面代碼中,字符a在字符串s的正確位置序號應該是 1,可是必須向codePointAt方法傳入 2。解決這個問題的一個辦法是使用for...of循環,由於它會正確識別 32 位的 UTF-16 字符。

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

codePointAt方法是測試一個字符由兩個字節仍是由四個字節組成的最簡單方法。

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

is32Bit("𠮷") // true
is32Bit("a") // false

3.String.fromCodePoint()

ES5 提供String.fromCharCode方法,用於從碼點返回對應字符,可是這個方法不能識別 32 位的 UTF-16 字符(Unicode 編號大於0xFFFF)。

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

上面代碼中,String.fromCharCode不能識別大於0xFFFF的碼點,因此0x20BB7就發生了溢出,最高位2被捨棄了,最後返回碼點U+0BB7對應的字符,而不是碼點U+20BB7對應的字符。

ES6 提供了String.fromCodePoint方法,能夠識別大於0xFFFF的字符,彌補了String.fromCharCode方法的不足。在做用上,正好與codePointAt方法相反。

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

上面代碼中,若是String.fromCodePoint方法有多個參數,則它們會被合併成一個字符串返回。

注意,fromCodePoint方法定義在String對象上,而codePointAt方法定義在字符串的實例對象上。

4.字符串的遍歷器接口

ES6 爲字符串添加了遍歷器接口(詳見《Iterator》一章),使得字符串能夠被for...of循環遍歷。

for (let codePoint of 'foo') {
  console.log(codePoint)
}
// "f"
// "o"
// "o"

除了遍歷字符串,這個遍歷器最大的優勢是能夠識別大於0xFFFF的碼點,傳統的for循環沒法識別這樣的碼點。

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循環會認爲它包含兩個字符(都不可打印),而for...of循環會正確識別出這一個字符。

5.normalize()

許多歐洲語言有語調符號和重音符號。爲了表示它們,Unicode 提供了兩種方法。一種是直接提供帶重音符號的字符,好比Ǒ(\u01D1)。另外一種是提供合成符號(combining character),即原字符與重音符號的合成,兩個字符合成一個字符,好比O(\u004F)和ˇ(\u030C)合成Ǒ(\u004F\u030C)。

這兩種表示方法,在視覺和語義上都等價,可是 JavaScript 不能識別。

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

'\u01D1'.length // 1
'\u004F\u030C'.length // 2

上面代碼表示,JavaScript 將合成字符視爲兩個字符,致使兩種表示方法不相等。

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

上面代碼表示,NFC參數返回字符的合成形式,NFD參數返回字符的分解形式。

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

6.includes(), startsWith(), endsWith()

傳統上,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
s.includes('Hello', 6) // false

上面代碼表示,使用第二個參數n時,endsWith的行爲與其餘兩個方法有所不一樣。它針對前n個字符,而其餘兩個方法針對從第n個位置直到字符串結束。

7.repeat()

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

'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""

參數若是是小數,會被取整。

'na'.repeat(2.9) // "nana"

若是repeat的參數是負數或者Infinity,會報錯。

'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError

可是,若是參數是 0 到-1 之間的小數,則等同於 0,這是由於會先進行取整運算。0 到-1 之間的小數,取整之後等於-0repeat視同爲 0。

'na'.repeat(-0.9) // ""

參數NaN等同於 0。

'na'.repeat(NaN) // ""

若是repeat的參數是字符串,則會先轉換成數字。

'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"

8.padStart(),padEnd()

ES2017 引入了字符串補全長度的功能。若是某個字符串不夠指定長度,會在頭部或尾部補全。padStart()用於頭部補全,padEnd()用於尾部補全。

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

上面代碼中,padStart()padEnd()一共接受兩個參數,第一個參數是字符串補全生效的最大長度,第二個參數是用來補全的字符串。

若是原字符串的長度,等於或大於最大長度,則字符串補全不生效,返回原字符串。

'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'

若是用來補全的字符串與原字符串,二者的長度之和超過了最大長度,則會截去超出位數的補全字符串。

'abc'.padStart(10, '0123456789')
// '0123456abc'

若是省略第二個參數,默認使用空格補全長度。

'x'.padStart(4) // ' x'
'x'.padEnd(4) // 'x '

padStart()的常見用途是爲數值補全指定位數。下面代碼生成 10 位的數值字符串。

'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"

另外一個用途是提示字符串格式。

'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

9.matchAll()

matchAll方法返回一個正則表達式在當前字符串的全部匹配,詳見《正則的擴展》的一章。

相關文章
相關標籤/搜索