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
属性看出来。