前端字符编码小结

导语

本文源于微信游戏春节王者摇心愿活动英雄语音祝福自定义输入模块开发过程,对踩过的前端字符编码的坑进行记录总结。

Unicode 字符

Unicode(中文:万国码、国际码、统一码、单一码)是计算机科学领域里的一项业界标准。它对世界上大部分的文字系统进行了整理、编码,使得电脑能够用更为简单的方式来呈现和处理文字。

简单地来讲,Unicode 是一种字符编码,它规定用一个码点表示一个字符,其范围为 U+0000~ U+10FFFF , 能够表示超过100万个符号。Unicode 分红17个平面,其中第1个平面称谓基本平面(也称 BMP),其范围为 U+0000~ U+FFFF,另外16个平面称之为辅助平面,每一个辅助平面拥有65536(即 2^16)个字符。javascript

Unicode 只规定了字符编码,而并无规定具体的编码方式。所以就产生了不一样的编码方式,包括 UTF-八、UTF-1六、UTF-32 等等。html

UTF-16

本文主要介绍 UTF-16 编码,不涉及 UTF-八、UTF-32 等其余编码方式,须要扩展阅读请自行查阅。
UTF-16 是一种变长的编码方式,能够用2个字节或者4个字节来编码 Unicode 字符。UTF-16 使用两个字节编码 Unicode 字符中的基本平面的字符,使用 4 个字节编码 Unicode 字符中的辅助平面的字符。
乱码了,用截图前端

UTF-16 使用变长字节的编码方式,那么如何判断一个字符是基本平面字符仍是辅助平面字符?
UTF-16 规定了BMP中,从 U+D800U+DFFF 之间BMP的区段是永久保留不映射到字符,能够利用这段区间来编码辅助平面的字符。
简单来讲,从左到右扫描,发现前两个字节不在 U+D800U+DFFF 之UTF间,则可认定这两个字节组成了一个基本平面的字符,发现前两个字节处于 U+D800U+DFFF 之间,则须要读取下两个字节,拼凑成四个字节组成一个辅助平面的字符。java

前面提到辅助平面有16(即 2^4)个,每一个辅助平面拥有65536(即 2^16)个字符,所以辅助平面共有 2^20个字符,也就是说须要 20 位二进制位来对应这些字符。正则表达式

16 * 65536 = 2^4 * 2^16 = 2^20

U+D800U+DFFF 之间恰好有 2^11 个码元,所以 UTF-16 使用 U+D800U+DBFF 之间(共有2^10个)码元做为高位, U+DC00U+DFFF 之间(共有 2^10 个)做为低位,这样子高低位 4 个字节组成的编码方式(代理对)就能够表示一个辅助平面的字符了。微信

其中,辅助平面字符 Unicode 到 UTF-16 代理对的转换规则以下( c 表示 Unicode 的码元,H 表示代理对的高位字节,L 表示代理对的低位字节):编码

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

以上面的音乐字符为例,其 Unicode 字符的码元为 U+1F3B6,能够经过 https://codepoints.net/ 查询到对应字符信息spa

> H = Math.floor((0x1F3B6 - 0x10000) / 0x400) + 0xD800 
0xd83c

> L = (0x1F3B6 - 0x10000) % 0x400 + 0xDC00
0xdfb6

经过上面的转换规则能够算出其代理对为 \ud83c\udfb6.net

UCS-2

UCS-2 是 UTF-16 未出世以前的一种编码方式,能够简单理解为 UTF-16的子集。它采用定长2字节编码,所以只能表示基本平面的字符,对于辅助平面字符,它只能理解为这是 “两个基本平面字符” ,没法正常表示。设计

javascript的编码方式

好了,进入正题了。前面讲了 UTF-16 和 UCS-2,那么 javascript 究竟是采用什么编码的呢?
这个要分状况来说,javascript 引擎采用 UTF-16 编码,而 javascript 语言自己的设计是采用 UCS-2 编码方式
所以,当咱们使用 UCS-2 编码方式设计的 javascript 接口来处理 UTF-16 编码的字符,就会出现不少问题。
好比:
仍是乱码了

那么如何解决这二者编码方式不一致形成的问题呢,有两种方式:

ES6

新版本的ECMA Script提供了新的API来正确处理字符
你们好,我是截图

正则

利用正则表达式对其修正(项目也是采用这种方式)

var regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g
// 获取字符的长度
function countSymbols(string) {
    return string
        // 把代理对改成一个BMP的字符.
        .replace(regexAstralSymbols, '_')
        // …这时候取长度就妥妥的啦.
        .length;
}

// 获取前6个字符
function sliceSymbols(str, limit) {
    var output = [];
    var index = 0;
    var oldStr = str;
    str = str.replace(regexAstralSymbols, function(input, offset, match) {
        if( offset > index ) {
            output = output.concat(match.slice(index, offset).split(""));
        }
        index = offset + input.length;
        output.push(input)
        return "";
    });

    if( index < oldStr.length  ) {
        output = output.concat(oldStr.slice(index, oldStr.length).split(""));
    }
    return output.slice(0, limit).join("");
}

实现效果以下:
图片描述

上面的解决方法基本能够解决大部分的字符问题,可是在遇到某些emoji表情依然会有些问题。

emoji

emoji表情符号是一种象形文字(图片符号),一般以丰富多彩的形式呈现并在文本中之内联形式使用,起源于日本。Unicode 对 emoji 表情作了划分范围,大部分属于辅助平面字符,目前 Unicode 中收录的 emoji 表情达到了 2700多个。所以,在大部分状况下,使用UTF-16的代理对来处理emoji 表情是没有问题。但在 emoji 表情中,还存在着一些字符(Emoji Sequences),它们没有显示的样式,主要起着链接、控制等做用。目前有下面几种:

控制符 <U+FE0E> 和 <U+FE0F>

<U+FE0E>, 做用是让基础Emoji 变成更接近文本样式( text-style )。
<U+FE0F>, 做用则是让基础Emoji 变成更接近Emoji样式( emoji-style )。
图片描述

零宽链接符 <U+200D>

emoji 除了单个 emoji 符号,还能够经过零宽链接符将多个 emoji 链接成一个 emoji。好比 \ud83d\udc68是表示一个 man,\ud83c\udf93表示一个学士帽,这两个经过零宽链接符链接起来 \ud83d\udc68\u200d\ud83c\udf93就表示一个男学生了。
图片描述

所以,为了解决emoji这些Emoji Sequences,将正则进行扩展:

var regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF][\u200D|\uFE0F|\uFE0E]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g;

除了以上两种比较常见的 Emoji Sequences,其实还有 Keycap Sequence, Flag Sequence, Tag Sequence, Modifier Sequence等字符,能够参考这里

参考连接

https://mathiasbynens.be/note...
https://mathiasbynens.be/note...
https://codepoints.net/
http://www.alloyteam.com/2016...
http://unicode.org/emoji/
https://unicode.org/emoji/cha...
http://unicode.org/emoji/char...

相关文章
相关标签/搜索