跳转到内容

第四章 字符串的扩展

ES6 为字符串扩展了方法对象,本章节主要对这些方法对象做详细地讲述。

字符的 Unicode 表示法

JavaScript 中可以使用 \uxxxx 形式表示一个字符,其中 xxxx 代表字符的 Unicode 码点,但是这种表示法只适用于码点在 \u0000\uFFFF 之间的字符。对于超出这个范围的字符,需要使用双字节的形式来表示,例如 \uD842\uDFB7 表示字符 𠮷

在 ES6 中,可以通过将码点放入大括号来正确解读超出 \uFFFF 范围的字符,例如 \u{20BB7} 表示字符 𠮷

js
'\uD842\uDFB7' === '\u{20BB7}' // true

🧾 备注

大括号表示法与四字节的 UTF-16 编码是等价的。

除了 \uXXXX 和大括号表示法外,JavaScript还有其他几种表示字符的方法,包括 \z\x7A\u007A 等,它们都可以表示字符 z

codePointAt()

JavaScript 内部以 UTF-16 格式储存字符,每个字符固定为 2 个字节。对于 Unicode 码点大于 0xFFFF 的字符,JavaScript 会将它们视为 2 个字符来处理,且 charAt 方法无法读取整个字符;charCodeAt 方法只能返回前 2 个 字节和后 2 个字节。

js
let s = '𠮷'

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

ES6 提供了 codePointAt 方法来处理 4 字节(即 2 个字节)储存的字符,能够返回正确的码点值。该方法返回的是码点的十进制值,需要使用 toString 方法将其转换为十六进制值。

js
let s = '𠮷a'

// 十进制
s.codePointAt(0) // '134071'
s.codePointAt(1) // '57271'
s.codePointAt(2) // '97'

// 十六进制
s.codePointAt(0).toString(16) // '20bb7'
s.codePointAt(2).toString(16) // '61'

codePointAt 方法的参数仍不正确,字符 a 理应是 1,但是必须传 2。可以使用 for ... of 循环来正确处理 32 位的 UTF-16 字符。

js
let s = '𠮷a'

for(let str of s) {
  console.log(str.codePointAt(O).toString(16)); 
}
// 20bb7
// 61

codePointAt 方法也可以判断一个字符是否由 2 个字节还是 4 字节组成。

js
'a'.codePointAt > 0xFFFF // false
'𠮷'.codePointAt > 0xFFFF // true

String.fromCodePoint()

在 ES5 中,使用 String.fromCharCode 方法无法正确识别大于 0xFFFF 的码点(即 32 位的 UTF-16 字符)。这会导致高位被舍弃,返回错误的字符。

ES6 引入了 String.fromCodePoint 方法,可以正确识别大于 0xFFFF 的码点,解决了 String.fromCharCode 方法的限制。String.fromCodePoint 方法可以接收多个参数,将它们合并成一个字符串返回。

js
String.fromCodePoint(0x20BB7) // 𠮷

🔔 提示

String.fromCodePoint 在作用上与 codePointAt 相反。fromCodePoint 方法定义在 String 对象上,而 codePointAt 方法定义在字符串的 实例对象上。

字符串的遍历器接口

ES6 为字符串添加遍历接口,使得 for ... of 可以遍历字符串。

js
let str = 'dao'

for(let k of str) {
  console.log(k)
}

// d
// a
// o

除此之外,这个遍历器还可以识别 0xFFFF 的码点,而传统的 for 循环无法识别。

js
let txt = String.fromCodePoint(0x20BB7)

for(let i = 0; i < txt.length; i++) {
  console.log(txt[i]) // ''。识别失败
}

for(let k of txt) {
  console.log(k) // 𠮷
}

at()

在 ES5 中,字符串对象的 charAt 方法无法正确识别码点大于 0xFFFF 的字符。它只返回 UTF-16 编码中的第一个字节,导致无法显示正确的字符。

目前有一个提案,提出了字符串实例的 at 方法,可以正确识别 Unicode 编号大于 0xFFFF 的字符,并返回正确的字符。该方法可以通过使用垫片库(例如 http://github.com/es-shims/String.prototype.at) 来实现。

normalize()

Unicode 提供了两种方法来表示欧洲语言中的语调符号和重音符号:直接提供带重音符号的字符和使用合成字符将原始字符与重音符号合并。然而,JavaScript 无法识别合成字符,导致两种表示方法在视觉和语义上不等价。

为了解决这个问题,JavaScript 的字符串类型提供了 normalize 方法,可以将具有不同表示方法的字符统一为相同形式,即 Unicode 正规化。

js
'\u01D1'.normalize() === '\uoo4F\u030C'.normalize() // true

normalize 方法接受参数来指定不同的正规化方式,包括 NFC(标准等价合成)、NFD(标准等价分解)、NFKC(兼容等价合成)和 NFKD(兼容等价分解)。

需要注意的是,normalize 方法不能识别由多个字符合成的情况。在这种情况下,仍然需要使用正则表达式或通过 Unicode 编码区间判断。

总结起来,Unicode 提供了两种方法来表示欧洲语言中的语调符号和重音符号,JavaScript 提供了 normalize 方法来实现字符的统一表示,但对于多个字符合成的情况仍需采用其他方法。

includes()、 startsWith()、endsWith()

在之前 JavaScript 只能通过 indexOf 来判断某字符串内是否包含另一字符串的内容。ES6 提供了三个新的方法。

  • includes() :返回布尔值,表示该字符串内是否包含要查找的参数字符串
  • startsWith() :返回布尔值,表示该字符串头部是否是参数字符串
  • endsWith() :返回布尔值,表示该字符串尾部是否是参数字符串

他们都能接收两个参数:参数一是所要查找的字符串,参数二是开始搜索的位置。

js
var s = 'Hello world!'; 
s.startsWith('Hello') // true 
s.endsWith('!') // true 
s.includes('o') // true

s.startsWith('world', 6) //true 
s.endsWith('Hello', 5) // true 
s.includes('Hello', 6) // false

repeat()

repeat 方法用于返回一个新字符串,该字符串是原始字符串重复若干次后的结果。

js
'x'.repeat(3) // xxx
'hello'.repeat(2) // hellohello

当参数为小数时,会被取整。

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

然而,如果参数是负数或者 Infinity,将会报错。

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

但是当参数是 -0.9 时,由于会先进行取整运算,所以 repeat 视同为 'na'.repeat(-0),返回空字符串 ""。

此外,参数为 NaN 时也等同于空字符串,即 'na'.repeat(NaN) 返回 ""。

如果 repeat 的参数是字符串,则会先尝试将其转换成数字。

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

总结一句话,repeat 方法用于将原字符串重复指定次数,对参数进行了取整处理,并且能够处理各种类型的参数输入。

padStart()、padEnd()

在 ES2017 中,引入了字符串补全长度的功能,包括了 padStartpadEnd 两个方法。

padStart 方法用于在字符串头部进行补全,而 padEnd 则用于在字符串尾部进行补全。这两个方法接受两个参数:第一个参数用来指定最小长度,第二个参数是用来补全的字符串。

如果原字符串的长度等于或大于指定的最小长度,则返回原字符串,不进行补全。如果补全的字符串与原字符串长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。

js
'x'.padStart(6, 'dao') // daodax
'x'.padEnd(6, 'dao') // xdaoda

'x'.padEnd(6, 'xiaodao') // xxiaod
'xiaodao'.padEnd(6, 'dao') // xiaodao

如果省略第二个参数,则默认使用空格来补全。

js
'x'.padStart(6) // '.     x'

padStart 方法常见的用途是为数值补全指定位数,例如生成固定位数的数值字符串。另外,也可以用于格式化日期字符串等,以提示字符串的格式。

js
'12'.padStart(5, '0') // 00012

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

总结一句话,padStartpadEnd 方法为字符串提供了补全长度的功能,可以用于各种不同类型的字符串格式化需求。

模板字符串

模板字符串可以用于创建普通字符串、多行字符串,或者在字符串中嵌入变量。在模板字符串中嵌入变量时,只需要将变量名写在 ${} 中,这样就可以很方便地构建动态字符串。

js
// 普通字符串
`hello world`

// 多行字符串
`this is line 1
this is line 2`

// 嵌入变量
let name = 'daodao', age = 24
`my name is ${name}, I'm ${age} years old`

此外,模板字符串还支持调用函数和嵌套。

js
// 调用函数
function fn() {
  return 123
}
`this number is ${fn()}` // this number is 123

// 嵌套
let str = `
<ul>
	${arr.map(item => `
		<li>${item}</li>
	`).join('')}
</ul>
`

在模板字符串中使用反引号时,如果需要输出反引号本身,则需要使用反斜杠进行转义。另外,模板字符串内部的空格和换行会被保留在输出中,但可以使用 trim() 方法去除额外的空格和换行。

js
// 转义
`my name is \` daodao \`` // `my name is ` daodao `

// 去除空格
`
<div>this is a line</div>
`.trim()

String.now()

ES6 引入了 String.raw() 方法,用于处理模板字符串。String.raw() 方法充当模板字符串的处理函数,返回一个反斜线都被转义的字符串,对应于替换变量后的模板字符串。

String.raw() 方法的基本使用方式是在模板字符串前面加上 String.raw 标签,并传入相应的参数。这些参数包括原始字符串和对应的变量值。该方法会将原始字符串中的反斜线进行转义,并将变量值插入到相应的位置。

如果原始字符串中的反斜线已经进行过转义,String.raw() 不会对其进行处理。

String.raw() 方法的代码实现如下:

javascript
String.raw = function (strings, ...values) {
  var output = "";
  for (var index = 0; index < values.length; index++) {
    output += strings.raw[index] + values[index];
  }
  output += strings.raw[index];
  return output;
}

String.raw() 方法可以作为处理模板字符串的基本方法,它会将所有变量替换,并对反斜线进行转义,方便作为字符串使用。

此外,String.raw() 方法也可以作为普通函数使用。这时,它的第一个参数应该是一个具有 raw 属性的对象,且 raw 属性的值应该是一个数组。通过这种方式,可以实现与标签模板字符串相同的效果。

总结一句话,String.raw() 方法是用于处理模板字符串的工具函数,它可以对模板字符串进行转义处理,并将变量值插入到相应位置。它是 ES6 中字符串处理的一部分。

模板字符串的限制

在标签模板中嵌入其他语言时,由于模板字符串默认会对特殊字符进行转义,导致无法直接嵌入。这种情况在处理类似 LaTeX 语言的场景中尤为突出。

举例来说,当尝试在模板字符串中嵌入 LaTeX 语言时,由于 JavaScript 引擎会对字符串进行转义,导致一些 LaTeX 中的特殊字符被解释错误,从而报错。例如,\unicode\xerxes 在 LaTeX 中是合法的命令,但在 JavaScript 中会被转义解析,导致报错。

为了解决这个问题,有一个提案被提出,即放松对标签模板内字符串转义的限制。根据这个提案,如果遇到不合法的字符串转义,JavaScript 引擎会返回 undefined 而不是报错,并且可以通过 raw 属性获取原始字符串。

举例来说,通过放宽字符串转义限制后,在处理模板字符串时即使出现类似不合法的转义字符,JavaScript 引擎不会报错,而是将其设置为 undefined;同时,通过 raw 属性仍然可以获取到原始字符串,保证对原字符串的正确处理。

js
function tag(strs) { 
	strs[O] ===undefined 
	strs.raw[O] ==='\\unicode and \\u{55}'; 
}
tag `\ unicode and \u{55}``

⚠ 注意

这种对字符串转义的放松只在标签模板解析字符串时生效,非标签模板的场合依然会报错。

总结

这一章节主要介绍了 ES6 对字符串的扩展方法对象,涵盖了以下的方法:

  1. Unicode 表示法:ES6 支持使用 \u{} 格式表示 Unicode 字符集中的字符。
  2. codePointAt() 方法:用于获取字符串中指定位置的 Unicode 码点。
  3. String.fromCodePoint() 方法:用于将一个或多个 Unicode 码点转化为对应的字符。
  4. 字符串的遍历器接口:ES6 将字符串也视为类数组对象,支持使用 for...of 循环和扩展运算符进行遍历。
  5. at() 方法:用于获取字符串中指定位置的字符。
  6. normalize() 方法:用于将含有不同表示方式的 Unicode 字符进行统一标准化处理。
  7. includes()startsWith()endsWith() 方法:分别用于判断字符串是否包含指定字符/子串、是否以指定字符/子串开头、是否以指定字符/子串结尾。
  8. repeat() 方法:用于将字符串重复指定次数。
  9. padStart()padEnd() 方法:分别用于在字符串的开头/结尾添加指定数量的字符,用于补全字符串。
  10. 模板字符串:ES6 支持使用反引号 `` 定义多行字符串,并且支持嵌入表达式。
  11. String.raw() 方法:用于获取模板字符串中的原始字面量。
  12. 模板字符串的限制:模板字符串中不能直接使用大括号 {} 进行运算,需要使用 ${} 进行表达式嵌入。

这些方法和特性丰富了 JavaScript 对字符串的处理能力,使得处理 Unicode 字符、字符串补全、模板字符串等操作更加简便和灵活。