ES6 字符串的新增方法

95 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情

ES6 字符串的新增方法

String.fromCodePoint() 方法

ES5 提供的 String.fromCharCode()  方法,用于从 Unicode 码点返回对应字符,但是这个方法不能识别码点大于 0xFFFF 的字符。


String.fromCharCode(0x20BB7)  // "ஷ"

上面代码中,String.fromCharCode()  不能识别大于 0xFFFF 的码点,所以 0x20BB7 就发生了溢出,最高位 2 被舍弃了,最后返回码点 U+0BB7 对应的字符,而不是码点 U+20BB7 对应的字符。

ES6 提供了 String.fromCodePoint()  方法,可以识别大于 0xFFFF 的字符,弥补了 String.fromCharCode()  方法的不足。在作用上,正好与下面的 codePointAt()  方法相反。


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

上面代码中,如果 String.fromCodePoint()  方法有多个参数,则它们会被合并成一个字符串返回。

> 注意,fromCodePoint 方法定义在 String 对象上,而 codePointAt()  方法定义在字符串的实例对象上。

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()  方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。

String.raw()  本质上是一个正常的函数,只是专用于模板字符串的标签函数。如果写成正常函数的形式,它的第一个参数,应该是一个具有 raw 属性的对象,且 raw 属性的值应该是一个数组,对应模板字符串解析后的值。


// `foo${1 + 2}bar`
// 等同于
String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"

上面代码中,String.raw()  方法的第一个参数是一个对象,它的 raw 属性等同于原始的模板字符串解析后得到的数组。

作为函数,String.raw()  的代码实现基本如下。

String.raw = function (strings, ...values) {
  let output = '';
  let index;
  for (index = 0; index < values.length; index++) {
    output += strings.raw[index] + values[index];
  }

  output += strings.raw[index]
  return output;
}

codePointAt() 方法

JavaScript 内部,字符以 UTF-16 的格式储存,每个字符固定为 2 个字节。对于那些需要 4 个字节储存的字符(Unicode 码点大于 0xFFFF 的字符),JavaScript 会认为它们是两个字符。

var 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()  方法只能分别返回前两个字节和后两个字节的值。

ES6 提供了 codePointAt()  方法,能够正确处理 4 个字节储存的字符,返回一个字符的码点。


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


codePointAt()  方法的参数,是字符在字符串中的位置(从 0 开始)。上面代码中,JavaScript 将“?a”视为三个字符,codePointAt()  方法在第一个字符上,正确地识别了“?”,返回了它的十进制码点 134071(即十六进制的20BB7)。在第二个字符(即“?”的后两个字节)和第三个字符“a”上,codePointAt()  方法的结果与 charCodeAt()  方法相同。

总之,codePointAt()  方法会正确返回 32 位的 UTF-16 字符的码点。对于那些两个字节储存的常规字符,它的返回结果与charCodeAt()  方法相同。

codePointAt()  方法返回的是码点的十进制值,如果想要十六进制的值,可以使用 toString()  方法转换一下。


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


你可能注意到了,codePointAt()  方法的参数,仍然是不正确的。比如,上面代码中,字符 a 在字符串 s 的正确位置序号应该是 1,但是必须向 codePointAt()  方法传入 2。解决这个问题的一个办法是使用 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