多字节字符

多字节字符

二月 10, 2022

在实现字符串脱敏需求时,遇到一个很有意思的问题:当我用String.prototype.subStr来获取字符串切片时,JavaScript并不能正确的识别emoji的长度。于是debug出以下规律。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var s = 'a';
console.log(s.length); // 1
console.log(s.subStr(0, 1)); // 'a'
console.log(s[0]);

var s1 = '😀'
console.log(s1.length); // 2
console.log(s1.subStr(0, 1)); // '\uD83D'
console.log(s1[0]); // '\uD83D'

var s2 = 'ผู้'
console.log(s2.length); // 3
console.log(s2.subStr(0, 1)); // 'ผ'
console.log(s2[0]); // 'ผ'

好家伙,完全没想到还能有这种问题。。

找了时间搜索一番,总结出以下知识点

本文的所有问题几乎都能在阮一峰的《Unicode与JavaScript详解》里找到答案

什么是Unicode、UTF-x

现在Unicode指的是一套字符集,UTF-8是Unicode字符集的一种实现方式。Unicode字符集规定了每个字符的码点(如
U+597D = 好),但到底用什么样的字节序表示这个码点,就是UTF-x干的事。

如UTF-32则完全对应Unicode编码。固定占用四个字节。四个字节一个码点。

UTF-8则是变长的编码方法,字符长度从1个字节到4个字节不等。越是常用的字符,字节越短(哈夫曼编码?)

UTF-16则是介于两者之间。Unicode中的”基本平面“占用2个字符。”辅助平面“占用4个字节。

基本平面、辅助平面的概念去阮老师博客里看。。

JavaScript用的什么编码

JavaScript用的叫UCS-2编码。因为历史原因。JavaScript诞生时,Unicode还未出现。但好在UCS-2后来合并到UTF-16里。可以简单的理解,JavaScript用的是UTF-16编码

所以可以简单的理解

很常见的中文、英文是写在基本平面,占用2个字节。而emoji是辅助平面内。占用4个字节。。

那么’ผู้’.length为啥为3?

这个应该要扯到Emoji Sequence,这篇文章扯到展开操作符:一家人就这么被拆散了
暂时不了解。暂时不展开讲。

打印出来的\uD83D代表什么

这是是码点表示法,写法是”反斜杠+u+码点“

在这里的”码点“是十六进制,与JavaScript中十六进制数字0xD83D一样。所以容易误解

几个常用的字符操作方法

String.fromCharCode

返回由指定的 UTF-16 代码单元序列创建的字符串

如果是双字节字符的第一个字符的画,则直接返回码点。否则可以返回对应的字符

String.fromCharCode('0xD83D') // '\uD83D'
String.fromCharCode(22909)    // '好'
String.fromCharCode('0x597D')    // '好'

可以接收多个参数

String.fromCharCode(22909, 22909); // ‘好好’

String.fromCodePoint

ES6加上的,为了解决双字节问题。基本签名同String.fromCharCode,多的功能是支持大于0xFFFF的码点。也就是直接输入双字节字符的码点。

String.fromCodePoint('0x1f601')    // '😁'
String.fromCharCode('0x1f601')    // ''
String.fromCharCode('0xf601') // ''

String.prototype.charAt

只是返回字符而已。效果同直接下标访问

var s1 = '😀'
s1.charAt(0) === s1[0]

String.prototype.CharCodeAt

返回一个字符串指定位置的码点(数字形式)

var s1 = '😀'
s1.charCodeAt(0)    //  55357

String.prototyoe.at

返回一个字符串指定字符的码点(码点表示法)

var s1 = '😀'
s1.at()    //    '\uD83D'
s1.at(1)    //    '\uDE00'
'\uD83D' + '\uDE00'    // '😀'
s1.at() + s1.at(1)    //     '😀' 

如何判断是否是双字节字符

开始笨办法

先Array.from()把字符串转成数组。

然后判断第二个字符是不是双字节字符:

1
2
3
4
5
var s = '12abcd中文😀0'

var arr = Array.from(s);

var isDoubleChar = arr[1] 1= s[1];

阮一峰老师给了更好的办法。

如果是双字节字符,则第一个字节必须是0xD8000xDBFF之间

1
2
3
4
5
6
7

var s = '12abcd中文😀0'

var char = s.charCodeAt(1);

var isDoubleChar = char <= 0xDBFF && char >= 0xD800;

原因后面可以补以下