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));
}


另一种方法也可以,使用扩展运算符(...)进行展开运算。

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


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

function is32Bit(c) {
return c.codePointAt(0) > 0xFFFF;
}

is32Bit("????") // true
is32Bit("a") // false


作者:jser_dimple