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方法返回一个正则表达式在当前字符串的全部匹配,详见《正则的扩展》的一章。

相关文章
相关标签/搜索