ES6详解(二)
字符串的扩展
字符串的遍历器接口
ES6 为字符串添加了遍历器接口(详见《Iterator》一章),使得字符串可以被for...of循环遍历。
1  | for (let codePoint of 'foo') {  | 
直接输入 U+2028 和 U+2029
JavaScript 字符串允许直接输入字符,以及输入字符的转义形式。举例来说,“中”的 Unicode 码点是 U+4e2d,你可以直接在字符串里面输入这个汉字,也可以输入它的转义形式\u4e2d,两者是等价的。
但是,JavaScript 规定有5个字符,不能在字符串里面直接使用,只能使用转义形式。
- U+005C:反斜杠(reverse solidus)
 - U+000D:回车(carriage return)
 - U+2028:行分隔符(line separator)
 - U+2029:段分隔符(paragraph separator)
 - U+000A:换行符(line feed)
 
举例来说,字符串里面不能直接包含反斜杠,一定要转义写成\\或者\u005c。
ES2019 允许 JavaScript 字符串直接输入 U+2028(行分隔符)和 U+2029(段分隔符)。
1  | const PS = eval("'\u2029'");  | 
模板字符串现在就允许直接输入这两个字符。另外,正则表达式依然不允许直接输入这两个字符,这是没有问题的,因为 JSON 本来就不允许直接包含正则表达式。
模板字符串
传统的 JavaScript 语言,输出模板通常是这样写的(下面使用了 jQuery 的方法)。
1  | $('#result').append(  | 
上面这种写法相当繁琐不方便,ES6 引入了模板字符串解决这个问题。
1  | $('#result').append(`  | 
1  | // 多行字符串  | 
上面代码中的模板字符串,都是用反引号表示。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。
1  | let greeting = `\`Yo\` World!`;  | 
如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。
1  | $('#list').html(`  | 
上面代码中,所有模板字符串的空格和换行,都是被保留的,比如<ul>标签前面会有一个换行。如果你不想要这个换行,可以使用trim方法消除它。
1  | $('#list').html(`  | 
模板字符串之中还能调用函数。
1  | function fn() {  | 
如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。
如果模板字符串中的变量没有声明,将报错。
1  | // 变量place没有声明  | 
由于模板字符串的大括号内部,就是执行 JavaScript 代码,因此如果大括号内部是一个字符串,将会原样输出。
1  | `Hello ${'World'}`  | 
模板字符串甚至还能嵌套。
1  | const tmpl = addrs => `  | 
如果需要引用模板字符串本身,在需要时执行,可以写成函数。
1  | let func = (name) => `Hello ${name}!`;  | 
上面代码中,模板字符串写成了一个函数的返回值。执行这个函数,就相当于执行这个模板字符串了。
字符串的新增方法
String.raw()
ES6 还为原生的 String 对象,提供了一个raw()方法。该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。
1  | String.raw`Hi\n${2+3}!`  | 
如果原字符串的斜杠已经转义,那么String.raw()会进行再次转义。
1  | String.raw`Hi\\n`  | 
实例方法:includes(), startsWith(), endsWith()
传统上,JavaScript 只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。
- **includes()**:返回布尔值,表示是否找到了参数字符串。
 - **startsWith()**:返回布尔值,表示参数字符串是否在原字符串的头部。
 - **endsWith()**:返回布尔值,表示参数字符串是否在原字符串的尾部。
 
1  | let s = 'Hello world!';  | 
这三个方法都支持第二个参数,表示开始搜索的位置。
1  | let s = 'Hello world!';  | 
上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。
实例方法:repeat()
repeat方法返回一个新字符串,表示将原字符串重复n次。
1  | 'x'.repeat(3) // "xxx"  | 
参数如果是小数,会被取整。
1  | 'na'.repeat(2.9) // "nana"  | 
如果repeat的参数是负数或者Infinity,会报错。
1  | 'na'.repeat(Infinity)  | 
但是,如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0,repeat视同为 0。
1  | 'na'.repeat(-0.9) // ""  | 
参数NaN等同于 0。
1  | 'na'.repeat(NaN) // ""  | 
如果repeat的参数是字符串,则会先转换成数字。
1  | 'na'.repeat('na') // ""  | 
实例方法:padStart(),padEnd()
ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。
1  | 'x'.padStart(5, 'ab') // 'ababx'  | 
上面代码中,padStart()和padEnd()一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。
如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串。
1  | 'xxx'.padStart(2, 'ab') // 'xxx'  | 
如果用来补全的字符串与原字符串,两者的长度之和超过了最大长度,则会截去超出位数的补全字符串。
1  | 'abc'.padStart(10, '0123456789')  | 
如果省略第二个参数,默认使用空格补全长度。
1  | 'x'.padStart(4) // ' x'  | 
实例方法:trimStart(),trimEnd()
ES2019 对字符串实例新增了trimStart()和trimEnd()这两个方法。它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
1  | const s = ' abc ';  | 
上面代码中,trimStart()只消除头部的空格,保留尾部的空格。trimEnd()也是类似行为。
除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。
浏览器还部署了额外的两个方法,trimLeft()是trimStart()的别名,trimRight()是trimEnd()的别名。
实例方法:replaceAll()
历史上,字符串的实例方法replace()只能替换第一个匹配。
1  | 'aabbcc'.replace('b', '_')  | 
上面例子中,replace()只将第一个b替换成了下划线。
如果要替换所有的匹配,不得不使用正则表达式的g修饰符。
1  | 'aabbcc'.replace(/b/g, '_')  | 
正则表达式毕竟不是那么方便和直观,ES2021 引入了replaceAll()方法,可以一次性替换所有匹配。
1  | 'aabbcc'.replaceAll('b', '_')  | 
它的用法与replace()相同,返回一个新字符串,不会改变原字符串。
replaceAll()的第二个参数replacement除了为字符串,也可以是一个函数,该函数的返回值将替换掉第一个参数searchValue匹配的文本。
1  | 'aabbcc'.replaceAll('b', () => '_')  | 
上面例子中,replaceAll()的第二个参数是一个函数,该函数的返回值会替换掉所有b的匹配。
实例方法:at()
at()方法接受一个整数作为参数,返回参数指定位置的字符,支持负索引(即倒数的位置)。
1  | const str = 'hello';  | 
如果参数位置超出了字符串范围,at()返回undefined。
正则的扩展
RegExp
ES5 中,RegExp构造函数有两种情况
第一种情况是,参数是字符串,这时第二个参数表示正则表达式的修饰符(flag)。
1  | var regex = new RegExp('xyz', 'i');  | 
第二种情况是,参数是一个正则表示式,这时会返回一个原有正则表达式的拷贝。
1  | var regex = new RegExp(/xyz/i);  | 
但是,ES5 不允许此时使用第二个参数添加修饰符,否则会报错。
1  | var regex = new RegExp(/xyz/, 'i');  | 
ES6 改变了这种行为。如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。
1  | new RegExp(/abc/ig, 'i').flags  | 
上面代码中,原有正则对象的修饰符是ig,它会被第二个参数i覆盖。
字符串的正则方法
ES6 出现之前,字符串对象共有 4 个方法,可以使用正则表达式:match()、replace()、search()和split()。
ES6 将这 4 个方法,在语言内部全部调用RegExp的实例方法,从而做到所有与正则相关的方法,全都定义在RegExp对象上。
Unicode 字符表示法
ES6 新增了使用大括号表示 Unicode 字符,这种表示法在正则表达式中必须加上u修饰符,才能识别当中的大括号,否则会被解读为量词。
1  | /\u{61}/.test('a') // false  | 
上面代码表示,如果不加u修饰符,正则表达式无法识别\u{61}这种表示法,只会认为这匹配 61 个连续的u。
RegExp.prototype.unicode 属性
正则实例对象新增unicode属性,表示是否设置了u修饰符。
1  | const r1 = /hello/;  | 
上面代码中,正则表达式是否设置了u修饰符,可以从unicode属性看出来。


