ES6 学习笔记 (中)| 青训营笔记

120 阅读8分钟

大括号内部可以放入任意的 JS 表达式,可以进行运算,以及引用对象属性。

let x = 1;
let y = 2;
​
`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// "3"

模板字符串之中还能调用函数。

function fn() {
  return "Hello World";
}
​
`foo ${fn()} bar`
// foo Hello World bar

如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。

如果模板字符串中的变量没有声明,将报错。

// 变量place没有声明
let msg = `Hello, ${place}`;
// 报错

由于模板字符串的大括号内部,就是执行 JavaScript 代码,因此如果大括号内部是一个字符串,将会原样输出。

`Hello ${'World'}`
// "Hello World"

模板字符串甚至还能嵌套。

const tmpl = addrs => `
  <table>
  ${addrs.map(addr => `
    <tr><td>${addr.first}</td></tr>
    <tr><td>${addr.last}</td></tr>
  `).join('')}
  </table>
`;

上面代码中,模板字符串的变量之中,又嵌入了另一个模板字符串,使用方法如下。

const data = [    { first: '<Jane>', last: 'Bond' },    { first: 'Lars', last: '<Croft>' },];
​
console.log(tmpl(data));
// <table>
//
//   <tr><td><Jane></td></tr>
//   <tr><td>Bond</td></tr>
//
//   <tr><td>Lars</td></tr>
//   <tr><td><Croft></td></tr>
//
// </table>

如果需要引用模板字符串本身,在需要时执行,可以写成函数。

let func = (name) => `Hello ${name}!`;
func('Jack') // "Hello Jack!"

上面代码中,模板字符串写成了一个函数的返回值。执行这个函数,就相当于执行这个模板字符串了。

字符串的新增方法

String.fromCodePoint()

首先,String.fromCharCode()用于从Unicode码点返回对应字符,但是它的识别码点不能大于0xFFFF

因此出现了String.fromCodePoint()方法,他可以识别大于0xFFFF的码点。

以下是使用方法。

String.fromCodePoint(0x20BB7)
// "𠮷"
String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y'
// true

String.raw()

ES6 还为原生的 String 对象,提供了一个raw()方法。该方法返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,往往用于模板字符串的处理方法。

String.raw`Hi\n${2+3}!`
// 实际返回 "Hi\n5!",显示的是转义后的结果 "Hi\n5!"String.raw`Hi\u000A!`;
// 实际返回 "Hi\u000A!",显示的是转义后的结果 "Hi\u000A!"

如果原字符串的斜杠已经转义,那么String.raw()会进行再次转义。

String.raw`Hi\n`
// 返回 "Hi\\n"String.raw`Hi\n` === "Hi\\n" // true

也就是说只要是使用了String.raw()这个方法,那么最后显示的效果与括号内输入的一致。

实例方法:codePointAt()

codePointAt()方法与上面两种方法不同的是,它属于实例方法,而上面两种方法是定义在String对象的,而codePointAt()则是定义在字符串的实例对象上的。

在 JS 内部中,字符串都是以 UTF-16 来进行存储的,也就是说每个字符占两个字节,但是如果一个字符是占四个字节的,也就是码点大于0xFFFF的字符,那么 JS 就会将它误判成两个字符。

var s = "𠮷";
​
s.length // 2
s.charAt(0) // ''
s.charAt(1) // ''
s.charCodeAt(0) // 55362
s.charCodeAt(1) // 57271

charCodeAt()方法只能分别返回前两个字节和后两个字节的值。

codePointAt()能够正确处理四个字节的字符,返回这个字符的码点。

let s = '𠮷a';
​
s.codePointAt(0) // 134071
s.codePointAt(1) // 57271
​
s.codePointAt(2) // 97

JS 会将 s 识别成三个字符,而使用codePointAt()能够识别第一个字符,也就是'𠮷',但是当要使用它来识别第二个和第三个字符的时候,则与charCodeAt()相同,当运行s.codePointAt(1)的时候,他只会识别'𠮷'的后两个字节并获取它的码点,也就是57271。

为了能够正确获取到字符 a 的码点,如果前面是四个字节的字符,那么如果这个字符起始占位是0,获取 a 的码点就应该从2来获取,也就是s.codePointAt(2)

codePointAt()方法返回的是十进制的值,如果想要获取十六进制,那么可以通过toString()来获取,如下。

let s = '𠮷a';
​
s.codePointAt(0).toString(16) // "20bb7"
s.codePointAt(2).toString(16) // "61"

上面我们提到了如果在s.codePointAt(1)当中是没有办法直接获取字符 a 的码点的,但是我们可以通过for...of循环来实现,他能够正确识别32位的UTF-16字符。

let s = '𠮷a';
for (let ch of s) {
  console.log(ch.codePointAt(0).toString(16));
}
// 20bb7
// 61

或者使用拓展运算符...进行展开运算。

let arr = [...'𠮷a']; // arr.length === 2
arr.forEach(
  ch => console.log(ch.codePointAt(0).toString(16))
);
// 20bb7
// 61

codePointAt()方法是测试一个字符由两个字节还是由四个字节组成的最简单方法。

function is32Bit(c) {
  return c.codePointAt(0) > 0xFFFF;
}
​
is32Bit("𠮷") // true
is32Bit("a") // false

上main定义了一个is32Bit()的函数,作用时用来判断字符是占两个字节还是四个字节,实现原理就是使用codePointAt()方法能够正确识别四个字节字符的特征。

实例方法:normalize()

ES6 提供字符串实例的normalize()方法,用来将字符的不同表示方法统一为同样的形式,这称为 Unicode 正规化。

实例方法:includes(), startsWith(), endsWith()

传统的 JS 当中的indexOf()方法(如果括号中的字符存在于字符串中,那么返回它在字符串中第一次出现时候的下标,如果没有出现则返回-1),然而 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

个人理解:此处s.startsWith('world', 6)表示的是从第六个字符往后开始考虑,也就是说第六个字符(空格)是不考虑的,如果换成s.startsWith('world', 7)那么结果就是错误的,includes()同理。

endsWith()使用第二个参数的时候,第二个参数表示的是结尾处,如上方代码表示的是从字符串开头,到第五个字符,此时第五个字符是考虑的,与startsWith()不同。

实例方法:repeat()

repeat(n)会将字符串重复 n 次,重复几次的意思其实就是打印几次。

'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""

如果括号内参数为小数会取整。

'na'.repeat(2.9) // "nana"

由上方代码可知此处取整是简单的将小数部分截取掉。

'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError

上方代码中使用了负数和Infinity会报错。

但是,如果参数是 0 到-1 之间的小数,则等同于 0,这是因为会先进行取整运算。0 到-1 之间的小数,取整以后等于-0repeat视同为 0。

'na'.repeat(-0.9) // ""

参数NaN等同于 0。

'na'.repeat(NaN) // ""

如果repeat的参数是字符串,则会先转换成数字,如果字符串能正常抓换成数字,那么也就可以正常执行。

'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"

实例方法:padStart(),padEnd()

这两个方法用于补全字符串,一个是用于在字符串头部补全,一个是用于在字符串尾部补全。

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax''x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

这两个方法的第一个参数是字符串的最长长度,第二个参数是用于补全的字符。

如果原来的字符串长度已经大于等于最长长度了,那么返回值是原值。

如果用来补全的字符串加上原来的字符串将超出最长长度,那么将会把用来补全的字符串截取掉多出来的那部分。

'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"

实例方法:trimStart(),trimEnd

ES2019 对字符串实例新增了trimStart()trimEnd两个方法,使用效果与trim()类似,trimStart()用于消除头部的空格,trimEnd用于消除尾部的空格。

他们的返回值都是新的字符串,而与原来的字符串无关。

除了空格,上面三个方法对于 tab 键、换行符等不可见的空白符号都是有效的。

浏览器还部署了额外的两个方法,trimLeft()trimStart()的别名,trimRight()trimEnd()的别名。

实例方法:replaceAll()

历史上,字符串的实例方法replace()只能替换第一个匹配。

'aabbcc'.replace('b', '_')
// 'aa_bcc'

上面例子中,replace()只将第一个b替换成了下划线。

如果要替换所有的匹配,不得不使用正则表达式的g修饰符。

'aabbcc'.replace(/b/g, '_')
// 'aa__cc'

正则表达式毕竟不是那么方便和直观,ES2021 引入了replaceAll()方法,可以一次性替换所有匹配。

'aabbcc'.replaceAll('b', '_')
// 'aa__cc'
'aabbcc'.replaceAll('b', () => '_')
// 'aa__cc'

replaceAll()的第二个参数可以是函数,像上面代码一样,replaceAll()会将这个匿名函数的返回值用于替换第一个参数。

实例方法:at()

at()方法用于返回字符串中对应下标的字符。

const str = 'hello';
str.at(1) // "e"
str.at(-1) // "o"

支持负索引,如果是负数那么是从后往前寻找。