[关闭]
@Dale-Lin 2018-06-10T14:12:44.000000Z 字数 6158 阅读 974

ES6 字符串和正则表达式

深入理解ES6


ES6强制使用UTF-16字符串编码,并按照这种字符串编码来标准化字符串操作。

codePointAt() 方法

ES6新增了完全支持UTF-16的 codePointAt() 方法,这个方法接受编码单位的位置作为参数,返回与字符串中给定位置对应的码位:

  1. let text = '槑a';
  2. text.codePointAt(0); //27089
  3. text.codePointAt(1); //97

对于BMP字符集包含的字符,codePointAt() 方法的返回值和 charCodeAt() 方法的相同,而对于非BMP字符集来说则不同。

要检测一个方法占用的编码单位数量,最简单的方法是调用字符的 codePointAt() 方法,可以通过一个函数来检测:

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

用16位表示的字符集上界为十六进制 FFFF,超过这个上界的码位一定由两个编码单元来表示,总共有32位。

String.fromCodePoint() 方法

String.fromCodePoint() 方法接受一个字符的码位,返回这个码位对应的字符。

normalize()方法

normalize() 方法提供 Unicode 的标准化形式。这个方法接受一个可选的字符串参数,指明应用以下的某种 Unicode 标准化形式:

  1. let normalized = value.map(function(text) {
  2. return text.normalize();
  3. });
  4. normalized.sort(function(first, second){
  5. if (first < second){
  6. return -1;
  7. } else if (first == second){
  8. return 0;
  9. } else {
  10. return 1;
  11. }
  12. });

在对比字符串之前,一定要将他们标准化为同一种形式。

上例将 value 数组中标准化后的字符串进行排序。

如果想排序原数组,在比较函数内使用 normalize() 方法即可:

  1. value.sort(function(first, second){
  2. let firstNormalized = first.normalize(),
  3. secondNormalized = second.normalize();
  4. if (firstNormalized < secondNormalized){
  5. return -1;
  6. } else if (firstNormalized == secondNormalized){
  7. return 0;
  8. } else {
  9. return 1;
  10. }
  11. });

正则表达式 u 修饰符

正则表达式可以完成简单的字符串操作,但默认将字符串的每一个字符按照16位编码单元处理。

u修饰符

ES6给正则表达式定义了一个支持 Unicode 的 u 修饰符。当一个正则表达式修饰符时,它就从编码单元操作模式切换成字符模式,以保证按预期正常运行:

  1. let text = 'a';
  2. console.log(/^.&/u.test(text)); //true

其他字符串变更

indexOf() 方法用来在一段字符串中检测另一段子字符串;在ES6中,提供了以下3个类似方法可达到相同效果:

以上三个方法都接受两个参数:第一个指定要搜索的文本;第二个参数是可选的,指定一个开始搜索的位置的索引值。如果指定了第二个参数,则 includes() 方法和 stratsWith() 方法会从这个索引值的位置开始匹配,endsWith() 方法则从字符串长度减去这个索引值的位置开始匹配。

要寻找指定字符串的位置,还是要用 indexOf() 或 lastIndexOf() 方法。
注意! includes() / startsWith() / endsWith() 方法参数必须是一个字符串,否则会触发一个错误,而 indexOf() / lastIndexOf() 方法的参数可以是正则表达式。

repeat() 方法

repeat() 方法接受一个 number 类型的参数,表示该字符串的重复次数,返回值是当前字符串重复一定次数后的新字符串:

  1. console.log('x'.repeat(3)); //'xxx'

在操作文本时比较方便,例如在代码格式化工具中创建缩进级别:

  1. let indent = ' '.repeat(4),
  2. indentLevel = 0;
  3. //当需要增加缩进时
  4. let newIndent = indent.repeat(++indentLevel);

其他正则表达式语法变更

y 修饰符

y 修饰符又称粘滞(sticky)符,只会从 lastIndex 的位置开始匹配,若未匹配成功,则不再尝试下一位置(与 g 的区别):

  1. let stickyPattern = /hello\d\s?/y,
  2. text = 'hello1 hello2';
  3. stickyPattern.exec(text); //hello1
  4. stickyPattern.lastIndex = 1;
  5. stickyPattern.exec(text); //null

可以像检查其它正则表达式修饰符那样检测y修饰符是否存在:

  1. let pattern = /hello\d\s?/y;
  2. console.log(pattern.sticky); //true

也可以用来检测是否支持该属性。

正则表达式的复制

ES5 中,可以通过向 RegExp 构造函数传递正则表达式作为参数以复制:

  1. var re1 = /ab/i,
  2. re2 = new RegExp(re1);

此处如果给 RegExp 构造函数传递第二个参数(为正则表达式指定一个修饰符),则会抛出错误:

  1. var re1 = /ab/i,
  2. //在ES5中抛出错误,在ES6中正常运行
  3. re2 = new RegExp(re1, g);

即在 ES6 中,即使构造函数第一个参数是一个正则表达式,也可以通过第二个参数修改其修饰符:

  1. let re1 = /ab/i,
  2. re2 = new RegExp(re1, 'g');
  3. console.log(re1.toString()); //'/ab/i'
  4. console.log(re2.toString()); //'/ab/g'

如果不传入第二个参数,则 re1 和 re2 使用相同的修饰符。

flags 属性

为配合新加入的修饰符,ES6 还新增了一个与之相关的新属性。
在 ES5 中,可以通过RegExp.source来获取正则表达式的文本,但要获取修饰符,需要用toString()方法来输出文本:

  1. function getFlags(re){
  2. var text = re.toString();
  3. return text.substring(text.lastIndexOf('/') + 1, text.length);
  4. }
  5. //toString() 的返回值为 "/ab/g"
  6. var re = /ab/g;
  7. console.log(getFlags(re)); //g

新增的 falgs 属性和 sourse 属性都是只读的原型属性访问器,对其只定义了 getter 方法,极大的简化了调试和编写继承代码的复杂度。

访问 flags 属性会返回所有应用于当前正则表达式的修饰符字符串:

  1. let re = /ab/g;
  2. console.log(re.source); //'ab'
  3. console.log(re.falgs); //'g'

模板字面量

基础语法

模板字面量最简单的用法,看起来只是用反撇号(`)替换了单、双引号:

  1. let message = `Hello World!`;
  2. console.log(message, typeof message, message.length);
  3. //"Hello World!" "string" 12

多行字符串

如果使用单、双引号,字符串一定要在同一行。
ES6 之前要实现多行字符串,在一个新行前添加反斜杠(\)可以承接上一行的代码,其实这是一个语法bug:

  1. var messagge = "Multiline \
  2. string.";
  3. console.log(message); //"Multiline string"

这个反斜杠只表示行的延续,并不能真正代表新的一行。
如要输出为新行,需要手动加入换行符:

  1. var message = "Multiline \n\
  2. string.";
  3. console.log(message); //"Multiline
  4. //string."

或用数组/字符串拼接:

  1. var message = [
  2. "Multiline ",
  3. "string."
  4. ].join("\n");
  5. var message = "Multiline \n" +
  6. "string.";

这样输入和输出都是多行字符串。

bug 语法应该避免。

简化多行字符串

ES6 模板字面量可以在代码中直接换行,并且换行直接同步出现在结果中:

  1. //换行处算一个字符,注意长度
  2. let message = `Multiline
  3. string.`;
  4. console.log(message); //"Multiline
  5. //string."
  6. console.log(message.length); //17

在反撇号中所有空白符都算字符串的一部分,所以注意缩进!

如果要通过适当的缩进来对齐文本,可以在多行模板字面量的第一行留白,并在后面几行缩进:

  1. let html = `
  2. <div>
  3. <h1>title</h1>
  4. </div>
  5. `.trim();

最后调用 trim() 方法去除开头的空白。

也可以在模板字面量中使用换行符\n来显式地指明新行:

  1. let message = `Multiline\nstring;

字符串占位符

模板字面量中的占位符 ${} 功能,可以在一个模板字面量中,将任意合法的JavaScript表达式嵌入到占位符中,并将其作为字符串的一部分输出到结果中:

  1. let name = "Nicholas",
  2. message = `Hello, ${name}.`;
  3. console.log(message); //'Hello, Nicholas.'

表达式也可以是运算等复杂的 JavaScript 语句。

标签模板

每个模板标签都可以执行模板字面量上的转换并返回最终的字符串值。
标签是指在模板字面量第一个反撇号前方标注的字符串:

  1. let message = tag`Hello world.`;

这个示例中,应用于模板字面量`Hello world.`的模板字面量是tag

定义标签

标签可以是一个函数,调用时传入加工过的模板字面量各部分数据,但必须结合每个部分来创建结果。
第一个参数是一个数组,包含 Javascript 解释过后的字面量字符串,之后的所有参数都是每一个占位符到解释值。

标签函数通常使用不定参数来定义占位符参数,从而简化数据处理的过程。

  1. function tag(literals, ...substitutions){
  2. //return a string
  3. }

如下例:

  1. let count = 10,
  2. price = 0.25,
  3. message = passthru`${count} items cost $${(count * price).toFixed(2)}.`;

如果有一个名为 passthru() 的模板字面量标签函数,它会接受三个参数:

  1. 一个 literals 数组,包含:

    • 第一个占位符前的内容,上例中是空字符串("")
    • 第一、第二个占位符间的字符串,上例中是" items cost $"
    • 第二、第三个占位符间的字符串......
    • ......
    • 最后一个占位符后的字符串,上例中是"."
  2. 第二个参数是第一个占位符中变量 count 的解释值10,它也成为 substitutions 数组里的第一个元素;最后一个参数是最后一个占位符 (count * price).toFixed(2) 的解释值2.50,它是 substitutions 数组里的第二个元素。

literals 里的第一个元素是一个空字符串,确保了 literals[0] 总是字符串的开始,literals[literals.length - 1] 总是字符串的结尾。
subtitutions 的长度总比 literals 少一。

通过这种模式,可以将 literalssubtitutions 两个数组交织在一起重组字符串:

  1. function passthru(literals, ...substitution){
  2. let result = '';
  3. //根据substitutions的长度决定循环的执行次数
  4. for (let i = 0; i < substitutions.length; i++){
  5. result += literals[i];
  6. result += substitutions[i];
  7. }
  8. //合并最后一个
  9. result += literals[literals.length - 1];
  10. return result;
  11. }
  12. let count = 10,
  13. price = 0.25,
  14. message = passthru`${count} items cost $${(count * price).toFixed(2)}.`;
  15. console.log(message); //"10 items cost $2.50."

在模板自变量中使用原始值

模板标签同样可以访问原生字符串信息(可以访问到字符转义为等价字符前的原生字符串)。
最简单的例子是使用內建的 String.raw() 标签:

  1. let message1 = `Multiline\nstring`,
  2. message2 = String.raw`Multiline\nstring`;
  3. console.log(message1); //"Multiline
  4. //string"
  5. console.log(message2); //"Multiline\\nstring"

这段代码中,变量 message1 中的 \n 被解释为一个新行,而变量 message2 获取的是 \n 的原生形式"\n"(反斜杠和 n 字符),必要的时候可以像这样来检查原生的字符串信息。

原生字符串的信息同样会被传入模板标签,标签函数的第一个参数(是一个 literals 数组)有一个额外的 .raw 属性,是一个包含每一个字面值的原生等价信息的数组。
即每个 literals[i] 总有一个对应的 literals.raw[i]
可以使用一下代码模仿 String.raw()

  1. function raw(literals, ...substitutions){
  2. let result = "";
  3. for (let i = 0; i < substitutions.length; i++){
  4. result += literals.raw[i];
  5. result += substitutions[i];
  6. }
  7. result += literals.raw[literals.length-1];
  8. return result;
  9. }
  10. let message = raw`Multiline\nstring`;
  11. console.log(message); //"Multiline\\nstring"
  12. console.log(message.length); //17

使用 literals.raw 来输出字符串,所有的转义字符都会输出他们的原生形式。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注