字符的 Unicode 表示法
ES6里,只要将码点放入大括号,就能正确解读该字符
"\u{20BB7}" // "𠮷"
"\u{41}\u{42}\u{43}" // "ABC"
let hello = 123
hell\u{6F} // 123
codePointAt()
JavaScript内部,字符以 UTF-16 的格式储存,每个字符固定为2个字节。对于那些需要4个字节存储的字符(Unicode码点大于 0xFFFF的字符),JavaScript会认为它们是2个字符。
let s = "𠮷"
s.length // 2
s.charAt(0) // ""
s.charAt(1) // ""
s.charCodeAt(0) // 55362
s.charCodeAt(1) // 57271
上面的汉字"𠮷"的码点是 0x20BB7,UTF-16编码为0xD842 0xDFB7 (十进制为55362 57271),需要4个字节储存。
对于这种4个字节的字符, JavaScript 不能正确处理,字符串长度会被误判为2,而且charAt方法无法读取整个字符,charCodeAt方法只能分别返回前2个字节和后2个字节的值。
ES6提供了 codePointAt方法,能够正确处理4个字节存储的字符,返回一个字符的码点。
let s = "𠮷a"
s.codePointAt(0) // 134071
s.codePointAt(1) // 57271
s.codePointAt(2) // 97
上面的代码中,JavaScript将 "𠮷a" 视为3个字符。codePointAt方法在第一个字符上正确地识别了"𠮷",返回了它的十进制码点134071(即十六进制的20BB7)。在第二个字符(即"𠮷"的后2个字节)和第三个字符"a"上,codePointAt方法的结果与charCodeAt方法相同。
String.fromCodePoint()
ES5提供了 String.fromCharCode 方法,用于从码点返回对应字符,但是这个方法不能识别32位的UTF-16字符。(Unicode编号大于 0xFFFF )。
String.fromCharCode(0x20BB7) // "ஷ"
上面的代码中,String.fromCharCode 不能识别大于 0xFFFF 的码点,所以 0x20BB7就发生了溢出,最高位 2 被舍弃,最后返回码点 U+0BB7 对应的字符,而不是码点 U+20BB7 对应的字符。
ES6提供了 String.fromCodePoint 方法,可以识别大于 0xFFFF 的字符,弥补了 String.fromCharCode方法的不足。在作用上,正好与 codePointAt 方法相反。
String.fromCodePoint(0x20BB7) // "𠮷"
注意: fromCodePoint 方法定义在 String 对象上,而 codePointAt 方法定义在字符串的实例对象上。
字符串的遍历器接口
ES6为字符串添加了遍历器接口,使得字符串可以由 for ... of 循环遍历。
for(let codePoint of 'foo') {
console.log(codePoint)
}
// "f"
// "o"
// "o"
除了遍历字符串,这个遍历器最大的优点是可以识别大于 0xFFFF 的码点,传统的 for 循环无法识别这样的码点。
let text = String.fromCodePoint(0x20BB7)
for(let i=0;i<text.length;i++) {
console.log(text[i])
}
// ""
// ""
for(let i of text) {
console.log(i)
}
// "𠮷"
上面的代码中,字符串 text 只有一个字符,但是 for 循环会认为它包含 2 个字符(都不可打印),而 for ... of 循环会正确识别出这个字符。
at()
ES5对字符串对象提供了 charAt 方法,返回字符串给定位置的字符。该方法不能识别码点大于 0xFFFF 的字符。
'abc'.charAt(0) // "a"
'𠮷'.charAt(0) // "\uD842"
上面的代码中,charAt 方法返回的是 UTF-16编码的第一个字节,实际上是无法显示的。
at方法可以识别 Unicode 编号大于 0xFFFF的字符,返回正确的字符。
'abc'.at(0) // "a"
'𠮷'.at(0) // "𠮷"
inclueds()、startsWith()、endsWith()
传统上,JavaScript中只有 indexOf 方法可用来确定一个字符串是否包含在另一个字符串中。
ES6又提供了3种新方法。
includes(): 返回布尔值,表示是否找到了参数字符串。
startsWith(): 返回布尔值,表示参数字符串是否在源字符串的头部。
endsWith(): 返回布尔值,表示参数字符串是否在源字符串的尾部。
let s = "Hello world!"
s.startsWith('Hello') // true
s.endsWith('!') // true
s.inclueds('o') // true
这三个方法都支持第二个参数,表示开始搜索的位置。
let s = "Hello world!"
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
上面的代码表示,使用第二个参数 n 时,endsWith 的行为与其他两个方法有所不同。它针对前 n 个字符,而其他两个方法针对从第 n 个位置到字符串结束位置之间的字符。
repeat()
repeat 方法返回一个新字符串,表示将原字符串重复 n 次。
'x'.repeat(3) // 'xxx'
'hello'.repeat(2) // 'hellohello'
'na'.repeat(0) // ''
参数如果是小数,会被取整。
'na'.repeat(2.9) // 'nana'
如果 repeat 的参数是负数或者 Infinity, 会报错。
'na'.repeat(Infinity) // RangeError
‘na’.repeat(-1) // RangeError
但如果参数是 0 到 -1 之间的小数,则等同于0,这是因为会先进行取整运算。0到-1之间的小数取整以后等于-0,repeat视同为0。
'na'.repeat(-0.9) // ""
参数 NaN等同于0.
'na'.repeat(NaN) // ""
如果 repeat 的参数是字符串,则会先转换成数字。
'na'.repeat('na') // ''
'na'.repeat('3') // 'nanana'
padStart()、padEnd()
ES2017引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。
padStart()用于头部补全,padEnd()用于尾部补全。
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
上面的代码中,padStart 和 padEnd分别接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数则是用来补全的字符串。
如果原字符串的长度等于或大于指定的最小长度,则返回原字符串。
'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'
如果用来补全的字符串与原字符串的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。
'abc'.padStart(10, '0123456789') // '0123456abc'
如果省略第二个参数,则会用空格来补全。
'x'.padStart(4) // ' x'
'x'.padEnd(4) // 'x '
padStart 的常见用途是为数值补全指定位数。
'1'.padStart(2, '0') // '01'
模板字符串
模板字符串是增强版的字符串,用反引号( ` )标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
// 多行字符串
`In JavaScript this is
not legal.`
console.log(`string text line 1
string text line 2`)
// 字符串中嵌入变量
let name = 'Bob', time = 'today'
`Hello ${name}, how are you ${time}?`
如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出中。
$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`)
上面的代码中,所有模板字符串的空格和换行都是被保留的,比如<ul>标签前面会有一个换行。如果不想要这个换行,可以使用trim方法消除。
$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`.trim())
在模板字符串中嵌入变量,需要将变量名写在${}中。
function authorize(user, action) {
if(!user.hasPrivilege(action)) {
throw new Error(`User ${user.name} is not authorized to do ${action}.`)
}
}
大括号内可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。
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 addrs = `
<table>
${addrs.map(addr => `
<tr><td>${addr.first}</td></tr>
<tr><td>${addr.last}</td></tr>
`).join('')}
</table>
`