JavaScript下含有emoji字符串的处理

我心飞翔 分类:javascript

一、应用中遇到的问题

含有emoji的字符串长度计算不对,字符串截取出错

image.png

二、涉及的知识点

JS语言采用的是Unicode字符集,UCS-2(utf-16)的编码方式,为了解决遇到的问题,我们从以下四个知识点着手

Unicode字符集,emoji字符,Unicode编码方式UCS-2(UTF-16),JS字符处理

1. Unicode字符集

Unicode字符集为解决传统字符编码方案的局限而产生,用一个码点(code point)映射一个字符,一一对应。

码点范围是U+0000 - U+10FFFF。codepoints.net/ 网站可以查看每个字符对应的码点信息

Unicode字符集目前分了17个区,最前面的65536个字符在基本平面(BMP-Basic Multilingual Plane), 码点范围0 到216-1,即U+0000 - U+FFFF。剩余的字符都在辅助平面(SMP),有16个,码点范围U+010000 - U+10FFFF

2. emoji字符

emoji字符都在辅助平面,除了单个的emoji字符,还可以使用U+200D零宽连字(ZWJ)将两个emoji连起来,使其看起来像是一个emoji(不支持的系统会忽略零宽连字)

例如U+1F468男人、U+200D ZWJ、U+1F469女人、U+200D ZWJ、U+1F467女孩(👨‍👩‍👧)在系统支持的情况下会显示为一个男人一个女人和一个女孩组成的家庭emoji,而不支持的系统则会顺序显示这三个emoji(👨👩👧)

3. 字符集编码方式

Unicode只规定了每个字符的码点,没规定在计算机中怎么表示。计算机中用什么样的字节表示Unicode码点,就是Unicode的编码方式,常见的有UTF-8, UTF-16, UTF-32(UTF即Unicode transformation format)

UTF-8:变长,1-4 字节,根据码点范围,采用不同字节数

UTF-16:变长,2 或 4 字节,根据码点范围,采用不同字节数。

编码范围 字节数量
U+0000 - U+FFFF 2
U+10000 - U+10FFFF 4

UTF-32:定长,4 字节,浪费空间,没人用。

下面主要讲下和本文相关的UTF-16

UTF-16即把Unicode字符集的抽象码位映射为16位长的整数(即码元)的序列,用于数据存储或传递。Unicode字符的码位,需要1个或者2个16位长的码元来表示。基本平面字符有一个16位长的码元表示占2个字节(U+0000 - U+FFFF),辅助平面字符由一对16位长的码元(称作代理对urrogate pair)来表示占4个字节(U+010000- U+10FFFF)。

当我们遇到两个字节,怎么看出它本身是一个字符,还是需要跟其他两个字节放在一起解读?

在基本平面内,从U+D800 - U+DFFF之间的码位区段是永久保留不映射到Unicode字符。UTF-16就利用保留下来的0xD800-0xDFFF区块的码位来对辅助平面的字符的码位进行编码。即辅助平面的字符,前两个字节范围U+D800 - U+DBFF(成为高位H), 后两个字节范围U+DC00 - U+DFFF(成为低位L)。和字符的码点(用c表示)通过以下关系进行映射:

H = Math.floor((c-0x10000) / 0x400)+0xD800
L = (c - 0x10000) % 0x400 + 0xDC00

所以,当我们遇到两个字节,发现它的码点在U+D800 - U+DBFF之间,就可以断定,紧跟在后面的两个字节的码点,应该在U+DC00到U+DFFF之间,这四个字节必须放在一起解读。

4. JavaScript字符处理

早期有两个团队做统一字符集:Unicode团队(1988),UCS团队(1989)

相关的主要时间点如下

1990 UCS-2编码发布(只支持16位)

1995.5 JavaScript诞生

1996.7 UTF-16诞生,取代UCS-2,基本平面字符沿用UCS-2编码,辅助平面字符定义了4个字节的表示方法

JS出现时还没有UTF-16,所以采用的是UCS-2编码。UTF-16可看成是UCS-2的父集。在没有辅助平面字符(surrogate code points)前,UTF-16与UCS-2所指的是同一的意思。但当引入辅助平面字符后,就称为UTF-16了。

三、JS中和字符编码相关的一些坑

JS中由于字符编码产生了一些坑,ES6增加了对Unicode的支持,填了很多坑,但是对于组合emoji还是无法正确处理

1.String.prototype.length

length返回的是字符串中字符编码单元的数量,emoji在辅助平面,4个字节,2个码元(js UCS-2编码)。所以单个emoji长度返回2,组合的emoji中间用空连接符链接,返回长度是构成组合的emoji数量*2+链接符(两个emoji中间有一个链接符)

可以用ES6的展开符... 或者Array.from转为数组在计算length,两者表现一致,但是遇到组合emoji会被拆开

image.png

2.字符串处理相关

String.prototype.substr()

String.prototype.substring()

String.prototype.split()

String.prototype.slice()

同样可以用ES6的扩展符...和Array.from转为数组在截取,组合emoji没法处理,如下图

image.png

3.码点和字符的互转

String.property.charAt()

String.fromCharCode()

ES6的codePointAt(), fromCodePoint可以解决这个问题

image.png

4. 字符串遍历

for in,可以用for of替代

image.png

5. 正则匹配

ES6给出正则后加个u解决

image.png

四、解决方案

有一些开源的处理方法如下, 主要通过判断码点范围,重新计算
github.com/dotcypress/…

github.com/sallar/stri…

还可以用lodash里面的toArray对字符串进行处理

五、补充

不同平台下的emoji表现

Unicode 只是规定了 Emoji 的码点和含义,并没有规定它的样式。不同系统会自己实现具体的样式,如下图所示。如果系统没有实现,就无法渲染,用户看到错误的样式。

image.png

参考资料

developer.mozilla.org/zh-CN/docs/…

www.unicode.org/emoji/chart…

codepoints.net/

apps.timwhitlock.info/emoji/table…

zh.wikipedia.org/wiki/Unicod…

zh.wikipedia.org/wiki/%E7%B9…

zh.wikipedia.org/wiki/UTF-16

mathiasbynens.be/notes/javas…

www.ruanyifeng.com/blog/2014/1…

stackoverflow.com/questions/5…

github.com/sallar/stri…

github.com/dotcypress/…

作者:掘金-JavaScript下含有emoji字符串的处理

回复

我来回复
  • 暂无回复内容