本文知识点主要整理自《深入理解 ES6(Understanding ECMAScript 6)》中文版实体书的内容,部分地方会加上自己的理解,同时书中叙述比较模糊的部分参考了阮一峰老师的《ECMAScript 6 入门》与网络上其他大佬们的博客、问答,篇幅有限无法一一列出,在此表示感谢。
更好的 Unicode 支持
UTF-16 码位与 ES5 的缺陷
Unicode 是计算机领域内的一项业界标准,它对世界上大部分的文字系统进行了整理、编码,目的是为世界上每一个字符提供全球唯一的标识符。Unicode 有两个步骤,一是给字符规定一个唯一对应的数字;二是将字符对应的数字保存在计算机中。
Unicode 对字符的唯一标识被称为码位(code point)。JavaScript 的字符串一直是基于 UTF-16 进行构建,每 16 位的序列视为一个编码单元(code unit),代表一个字符。并且 length
、charAt()
等字符串的属性与方法都是基于这种编码单元构造的。
在 UTF-16 中,字符编码空间由 U+0000
到 U+10FFFF
一共 1112064
个码位。同时编码空间一共分为 17 个平面(plane),每一个平面包含 U+xx0000
到 U+xxFFFF
一共 65536 个码位。而第一个平面(U+0000
到 U+FFFF
)被称为 基本多文种平面(Basic Multilingual Plane,简称 BMP)。其他平面被称为 辅助平面。
在早期 16 位足以包含任何字符,但随着计算机的发展,更多语言与符号的加入,16 位已经不够用,因此 UTF-16 加入了代理对(surrogate pair),其规定用两个 16 位编码单元表示一个码位。
因此,UTF-16 中有两种字符
- 由一个编码单元 16 位表示的 BMP 字符
- 由两个编码单元 32 为表示的辅助平面字符
在 ES5 中,如果遇到包含代理对的 UTF-16 字符可能会得到与预期不符的结果。
const text = '?'; // \uD842\uDFB7
console.log(text.length); // 2
console.log(text.charAt(0)); // ''
console.log(text.charAt(1)); // ''
console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271
复制代码
汉字“?”是通过代理对表示的字符,因此在长度上会被判定为 2,并且通过 charCodeAt
获得的两个编码单元都不表示任何可打印的字符。
codePointAt() 方法
在 ES6 中增加了完全支持 UTF-16 的 codePointAt
方法。不过这个方法的参数时 编码单位的位置,而非字符的位置。并且能够返回指定位置对应的码位。
const text = '?a';
console.log(text.codePointAt(0)); // 134071,这个整数即字符 ? 的码位
console.log(text.codePointAt(1)); // 57271
console.log(text.codePointAt(2)); // 97,? 是非 BMP 字符,因此占两个编码单位,要查看 a 的码位就需要传入 2
复制代码
用这个方法就可以判断字符是否是非 BMP 字符了。
function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}
console.log(is32Bit('?')); // true
console.log(is32Bit('a')); // false
复制代码
解读非 BMP 字符
'\uD842\uDFB7'; // ?
'\u20BB7'; // ₻7
复制代码
对于超出 0xFFFF
的字符,JavaScript 会理解为 0x20BB
和 0x7
两个字符,因此会是 ₻7
。
而在 ES6 中,将码位用 {}
进行包裹,就能正确解读出非 BMP 字符。
'\u{20BB7}'; // ?
复制代码
String.fromCodePoint() 方法
使用 codePointAt()
方法获得的码位,可以使用 String.fromCodePoint()
进行字符生成。
console.log(String.fromCodePoint(134071)); // "?"
复制代码
当传入 BMP 字符的码位时,输出结果与 String.fromCharCode()
一致,只有当传入非 BMP 字符的码位时,结果才会不同。
normalize() 方法
在某些时候,两个不一样的字符会表示同样的意思并且能够互换,而 JavaScript 并不能准确识别,比如字符 æ
和字符串 ae
,虽然严格来说二者并不等价,但在某些场景下二者是等效的。
为了解决这个问题,ES6 加入了 normalize()
方法,并且接收一个参数,指明只用以下的方式之一进行标准化:
- NFC,默认选项,以标准等价方式分解,然后以标准等价方式重组(所谓“标准等价”是指视觉和语义上的等价)
- NFD,以标准等价方式分解
- NFKC,以兼容等价方式分解
- NFKD,以兼容等价方式分解,然后以标准等价方式重组
关于 Unicode 字符标准化的问题与 ES6 关联较少,要注意的是在开发国际化应用时,在对比字符串之前,可以先对字符串使用 相同 的标准进行一次标准化,再进行比较。
不过标准化字符串接口 normalize()
也有一些缺陷
- 无法识别中文。
- 无法识别 3 个及以上的字符的合成。
字符串中子串的识别
在 ES5 以及之前的时间里,开发者大多使用 indexOf()
方法来检测字符串中的子字符串。ES6 中提供了 3 个类似的方法来达到相同的效果。
includes()
- 检测到指定的文本则返回true
,否则false
。startsWith()
- 在字符串开头检测到指定的文本返回true
,否则false
。endsWith()
- 在字符串结尾检测到指定的文本返回true
,否则false
。
这三个接口都有两个参数,第二个参数是可选的,用于指定一个开始位置的索引。
- 如果指定了第二个参数
n
,includes()
和startsWith()
的匹配范围是从n
到字符串结尾,否则从头开始。 - 如果指定了第二个参数
n
,endsWith()
的匹配范围是前n
个字符。
const msg = 'Hello';
console.log(msg.includes('ll')); // true
console.log(msg.startsWith('H'); // true
console.log(msg.endsWith('l', 3); // true
复制代码
如果往
includes()
、startsWith()
、endsWith()
传入一个正则表达式会报错。对indexOf()
与lastIndexOf()
传入正则表达式,它们会将正则表达式转为字符串,然后再进行匹配。
repeat() 方法
返回对当前字符串重复指定次数的新字符串。
const str = 'oop';
console.log(str.repeat(3)); // 'oopoopoop'
复制代码
模板字面量
基础语法
模板字面量的基础语法就是用反撇号代替字符串的单引号或者双引号。
const str = `Hello World`;
复制代码
在模板字符串中,反撇号需要使用反斜杠转义,而单引号与双引号不需要转义。
多行字符串
在模板字符串中可以直接对字符串换行,不过要注意,模板字符串中的每一个空格都会被视为字符串的一部分,因此使用模板字符串输入多行字符串时要注意字符串中的空格。
字符串占位符
字符串占位符可以将任何合法的 JavaScript 表达式嵌入到字符串中,并且作为字符串的一部分输出到结果。
字符串占位符的语法是 ${}
,中间可以是变量、运算式、函数调用等,如果嵌入的是对象等结果非字符串的内容,会采用 JavaScript 默认的规则将其转换为字符串(如对象是调用其 toString()
方法)。因为模板字面量本身也是 JavaScript 表达式,因此也可以进行嵌套
const a = 3;
const b = 2;
console.log(`a * b = ${a * b}`); // 'a * b = 6'
复制代码
只有当
$
后立马接{
才会被判断为字符串占位符,否则只是字符$
。
标签模板
字符串模板可以跟在一个函数名后,该函数将会被调用来处理这个字符串模板,这就是标签模板功能。
alert`apple`;
// 等同于
alert('apple');
复制代码
但是当字符串模板中有其他的变量或者占位符时,参数会被处理。
tag`Hello ${man}!`;
// 等同于
tag(['Hello ', '!'], man);
function tag(strArr, ...values) {
...
}
复制代码
标签模板的输出结果就取决于函数 tag
的输出结果。
在模板字面量中使用原始值
如果需要查看模板字面量转义前的原始值,可以使用 String.raw
标签
const message1 = `Multiline\nstring`;
const message2 = String.raw`Multiline\nstring`;
console.log(message1); // Multiline
// string
console.log(message2); // Multiline\\nstring
复制代码
参考资料
- UTF-16(维基百科)
- Unicode(UTF-8, UTF-16)令人混淆的概念
- Understanding ES6
- ECMAScript 6 入门 - 字符串的扩展