ES5
提供 String.fromCharCode()
方法,用于從 Unicode
碼點返回對應(yīng)字符
,但是這個方法不能識別碼點大于 0xFFFF 的字符。
String.fromCharCode(0x20BB7)
// "?"
上面代碼中, String.fromCharCode() 不能識別大于 0xFFFF 的碼點,所以 0x20BB7 就發(fā)生了溢出,最高位 2 被舍棄了,最后返回碼點 U+0BB7 對應(yīng)的字符,而不是碼點 U+20BB7 對應(yīng)的字符。
ES6
提供了 String.fromCodePoint()
方法,可以識別大于 0xFFFF 的字符,彌補(bǔ)了 String.fromCharCode() 方法的不足。在作用上,正好與下面的 codePointAt() 方法相反。
String.fromCodePoint(0x20BB7)
// "吉",注意,這個字符長得很像吉,但并不是漢字“吉”,為了展示出來以下這個符號用吉代替
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
// true
上面代碼中,如果 String.fromCodePoint 方法有多個參數(shù),則它們會被合并成一個字符串返回。
注意, fromCodePoint 方法定義在 String 對象上,而 codePointAt 方法定義在字符串的實例對象上。
ES6
還為原生的 String 對象,提供了一個 raw()
方法。該方法返回一個斜杠都被轉(zhuǎn)義(即斜杠前面再加一個斜杠)的字符串,往往用于模板字符串的處理方法。
String.raw`Hi\n${2+3}!`
// 實際返回 "Hi\\n5!",顯示的是轉(zhuǎn)義后的結(jié)果 "Hi\n5!"
String.raw`Hi\u000A!`;
// 實際返回 "Hi\\u000A!",顯示的是轉(zhuǎn)義后的結(jié)果 "Hi\u000A!"
如果原字符串的斜杠已經(jīng)轉(zhuǎn)義,那么 String.raw() 會進(jìn)行再次轉(zhuǎn)義。
String.raw`Hi\\n`
// 返回 "Hi\\\\n"
String.raw`Hi\\n` === "Hi\\\\n" // true
String.raw() 方法可以作為處理模板字符串的基本方法,它會將所有變量替換,而且對斜杠進(jìn)行轉(zhuǎn)義,方便下一步作為字符串來使用。
String.raw() 本質(zhì)上是一個正常的函數(shù),只是專用于模板字符串的標(biāo)簽函數(shù)。如果寫成正常函數(shù)的形式,它的第一個參數(shù),應(yīng)該是一個具有 raw 屬性的對象,且 raw 屬性的值應(yīng)該是一個數(shù)組,對應(yīng)模板字符串解析后的值。
// `foo${1 + 2}bar`
// 等同于
String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"
上面代碼中, String.raw() 方法的第一個參數(shù)是一個對象,它的 raw 屬性等同于原始的模板字符串解析后得到的數(shù)組。
作為函數(shù), String.raw() 的代碼實現(xiàn)基本如下。
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;
}
JavaScript 內(nèi)部,字符以UTF-16
的格式儲存,每個字符固定為 2 個字節(jié)。對于那些需要 4 個字節(jié)儲存的字符(Unicode 碼點大于 0xFFFF 的字符),JavaScript 會認(rèn)為它們是兩個字符。
var s = "吉";
s.length // 2
s.charAt(0) // ''
s.charAt(1) // ''
s.charCodeAt(0) // 55362
s.charCodeAt(1) // 57271
上面代碼中,漢字“吉”(注意,這個字不是“吉祥”的“吉”)的碼點是 0x20BB7 ,UTF-16 編碼為 0xD842 0xDFB7 (十進(jìn)制為 55362 57271 ),需要 4 個字節(jié)儲存。對于這種 4 個字節(jié)的字符,JavaScript 不能正確處理,字符串長度會誤判為 2 ,而且 charAt() 方法無法讀取整個字符, charCodeAt() 方法只能分別返回前兩個字節(jié)和后兩個字節(jié)的值。
ES6
提供了 codePointAt()
方法,能夠正確處理 4 個字節(jié)儲存的字符,返回一個字符的碼點。
let s = '吉a';
s.codePointAt(0) // 134071
s.codePointAt(1) // 57271
s.codePointAt(2) // 97
codePointAt() 方法的參數(shù),是字符在字符串中的位置(從 0 開始)。上面代碼中,JavaScript 將“吉a”視為三個字符,codePointAt 方法在第一個字符上,正確地識別了“吉”,返回了它的十進(jìn)制碼點 134071(即十六進(jìn)制的 20BB7 )。在第二個字符(即“吉”的后兩個字節(jié))和第三個字符“a”上, codePointAt() 方法的結(jié)果與 charCodeAt() 方法相同。
總之, codePointAt() 方法會正確返回 32 位的 UTF-16 字符的碼點。對于那些兩個字節(jié)儲存的常規(guī)字符,它的返回結(jié)果與 charCodeAt() 方法相同。
codePointAt() 方法返回的是碼點的十進(jìn)制值,如果想要十六進(jìn)制的值,可以使用 toString() 方法轉(zhuǎn)換一下。
let s = '吉a';
s.codePointAt(0).toString(16) // "20bb7"
s.codePointAt(2).toString(16) // "61"
你可能注意到了, codePointAt() 方法的參數(shù),仍然是不正確的。比如,上面代碼中,字符 a 在字符串 s 的正確位置序號應(yīng)該是 1,但是必須向 codePointAt() 方法傳入 2。解決這個問題的一個辦法是使用 for...of 循環(huán),因為它會正確識別 32 位的 UTF-16 字符。
let s = '吉a';
for (let ch of s) {
console.log(ch.codePointAt(0).toString(16));
}
// 20bb7
// 61
另一種方法也可以,使用擴(kuò)展運(yùn)算符( ... )進(jìn)行展開運(yùn)算。
let arr = [...'吉a']; // arr.length === 2
arr.forEach(
ch => console.log(ch.codePointAt(0).toString(16))
);
// 20bb7
// 61
codePointAt() 方法是測試一個字符由兩個字節(jié)還是由四個字節(jié)組成的最簡單方法。
function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}
is32Bit("吉") // true
is32Bit("a") // false
許多歐洲語言有語調(diào)符號和重音符號。為了表示它們,Unicode 提供了兩種方法。一種是直接提供帶重音符號
的字符,比如 ǒ (\u01D1)。另一種是提供合成符號
(combining character),即原字符與重音符號的合成,兩個字符合成一個字符,比如 O (\u004F)和 ˇ (\u030C)合成 ǒ (\u004F\u030C)。
這兩種表示方法,在視覺和語義上都等價,但是 JavaScript 不能識別。
'\u01D1'==='\u004F\u030C' //false
'\u01D1'.length // 1
'\u004F\u030C'.length // 2
上面代碼表示,JavaScript 將合成字符視為兩個字符,導(dǎo)致兩種表示方法不相等。
ES6 提供字符串實例的 normalize()
方法,用來將字符的不同表示方法統(tǒng)一為同樣的形式,這稱為 Unicode 正規(guī)化。
'\u01D1'.normalize() === '\u004F\u030C'.normalize()
// true
normalize 方法可以接受一個參數(shù)來指定 normalize 的方式,參數(shù)的四個可選值如下。
'\u004F\u030C'.normalize('NFC').length // 1
'\u004F\u030C'.normalize('NFD').length // 2
上面代碼表示, NFC 參數(shù)返回字符的合成形式, NFD 參數(shù)返回字符的分解形式。
不過, normalize 方法目前不能識別三個或三個以上字符的合成。這種情況下,還是只能使用正則表達(dá)式,通過 Unicode 編號區(qū)間判斷。
傳統(tǒng)上,JavaScript 只有 indexOf 方法,可以用來確定一個字符串是否包含在另一個字符串中。ES6
又提供了三種
新方法。
let s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
這三個方法都支持第二個參數(shù),表示開始搜索的位置。
let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
上面代碼表示,使用第二個參數(shù) n 時, endsWith 的行為與其他兩個方法有所不同。它針對前 n 個字符,而其他兩個方法針對從第 n 個位置直到字符串結(jié)束。
repeat
方法返回一個新字符串
,表示將原字符串重復(fù) n 次。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
參數(shù)如果是小數(shù),會被取整。
'na'.repeat(2.9) // "nana"
如果 repeat 的參數(shù)是負(fù)數(shù)或者 Infinity ,會報錯。
'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError
但是,如果參數(shù)是 0 到-1 之間的小數(shù),則等同于 0,這是因為會先進(jìn)行取整運(yùn)算。0 到-1 之間的小數(shù),取整以后等于 -0 , repeat 視同為 0。
'na'.repeat(-0.9) // ""
參數(shù) NaN 等同于 0。
'na'.repeat(NaN) // ""
如果 repeat 的參數(shù)是字符串,則會先轉(zhuǎn)換成數(shù)字。
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"
ES2017
引入了字符串補(bǔ)全長度
的功能。如果某個字符串不夠指定長度,會在頭部或尾部補(bǔ)全。 padStart()
用于頭部補(bǔ)全, padEnd()
用于尾部補(bǔ)全。
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
上面代碼中, padStart() 和 padEnd() 一共接受兩個參數(shù),第一個參數(shù)是字符串補(bǔ)全生效的最大長度,第二個參數(shù)是用來補(bǔ)全的字符串。
如果原字符串的長度,等于或大于最大長度,則字符串補(bǔ)全不生效,返回原字符串。
'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'
如果用來補(bǔ)全的字符串與原字符串,兩者的長度之和超過了最大長度,則會截去超出位數(shù)的補(bǔ)全字符串。
'abc'.padStart(10, '0123456789')
// '0123456abc'
如果省略第二個參數(shù),默認(rèn)使用空格補(bǔ)全長度。
'x'.padStart(4) // ' x'
'x'.padEnd(4) // 'x '
padStart() 的常見用途是為數(shù)值補(bǔ)全指定位數(shù)。下面代碼生成 10 位的數(shù)值字符串。
'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"
ES2019 對字符串實例新增了 trimStart()
和 trimEnd()
這兩個方法。它們的行為與trim()
一致, trimStart()
消除字符串頭部的空格,trimEnd()
消除尾部的空格。它們返回的都是新字符串,不會修改原始字符串。
const s = ' abc ';
s.trim() // "abc"
s.trimStart() // "abc "
s.trimEnd() // " abc"
上面代碼中, trimStart() 只消除頭部的空格,保留尾部的空格。 trimEnd() 也是類似行為。
除了空格鍵,這兩個方法對字符串頭部(或尾部)的 tab 鍵、換行符等不可見的空白符號也有效。
瀏覽器還部署了額外的兩個方法, trimLeft() 是 trimStart() 的別名, trimRight() 是 trimEnd() 的別名。
matchAll()
方法返回一個正則表達(dá)式
在當(dāng)前字符串的所有匹配
,詳見《正則的擴(kuò)展》的一章。
更多建議: