Created By JishuBao on 2019-04-02 12:38:22
Recently revised in 2019-04-03 12:38:22php
欢迎你们来到技术宝的掘金世界,您的star是我写文章最大的动力!GitHub地址 前端
文章简介:git
一、赶上emoji程序员
二、字符编码的故事github
三、字符编码详解算法
四、字符编码区别编程
五、谈谈emoji微信
最近闲来无事,在看源码,发现一个颇有意思的事情。网络
好久好久之前,有一群人,他们决定用8个能够开合的晶体管来组合成不一样的状态,以表示世界上的万物。他们看到8个开关状态是好的,因而他们把这称为”字节“。再后来,他们又作了一些能够处理这些字节的机器,机器开动了,能够用字节来组合出不少状态,状态开始变来变去。他们看到这样是好的,因而它们就这机器称为”计算机“。编辑器
开始计算机只在美国用。八位的字节一共能够组合出256(2的8次方)种不一样的状态。 他们把其中的编号从0开始的32种状态分别规定了特殊的用途,一但终端、打印机赶上约定好的这些字节被传过来时,就要作一些约定的动做:
赶上0×10, 终端就换行;
赶上0×07, 终端就向人们嘟嘟叫;
赶上0x1b, 打印机就打印反白的字,或者终端就用彩色显示字母。
他们看到这样很好,因而就把这些0×20如下的字节状态称为”控制码”。他们又把全部的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,这样计算机就能够用不一样字节来存储英语的文字了。你们看到这样,都感受 很好,因而你们都把这个方案叫作 ANSI 的”Ascii”编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上全部的计算机都用一样的ASCII方案来保存英文文字。
后来,就像建造巴比伦塔同样,世界各地都开始使用计算机,可是不少国家用的不是英文,他们的字母里有许可能是ASCII里没有的,为了能够在计算机保存他们的文字,他们决定采用 127号以后的空位来表示这些新的字母、符号,还加入了不少画表格时须要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一个状态255。从128 到255这一页的字符集被称”扩展字符集“。今后以后,贪婪的人类再没有新的状态能够用了,美帝国主义可能没有想到还有第三世界国家的人们也但愿能够用到计算机吧!
等中国人们获得计算机时,已经没有能够利用的字节状态来表示汉字,何况有6000多个经常使用汉字须要保存呢。可是这难不倒智慧的中国人民,咱们不客气地把那些127号以后的奇异符号们直接取消掉, 规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一块儿时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE,这样咱们就能够组合出大约7000多个简体汉字了。在这些编码里,咱们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里原本就有的数字、标点、字母都通通从新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号如下的那些就叫”半角”字符了。中国人民看到这样很不错,因而就把这种汉字方案叫作 “GB2312“。GB2312 是对 ASCII 的中文扩展。
可是中国的汉字太多了,咱们很快就就发现有许多人的人名没有办法在这里打出来,特别是某些很会麻烦别人的国家领导人。因而咱们不得不继续把GB2312 没有用到的码位找出来老实不客气地用上。后来仍是不够用,因而干脆再也不要求低字节必定是127号以后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,无论后面跟的是否是扩展字符集里的内容。结果扩展以后的编码方案被称为 GBK 标准,GBK包括了GB2312 的全部内容,同时又增长了近20000个新的汉字(包括繁体字)和符号。 后来少数民族也要用电脑了,因而咱们再扩展,又加了几千个新的少数民族的字,GBK扩成了 GB18030。今后以后,中华民族的文化就能够在计算机时代中传承了。 中国的程序员们看到这一系列汉字编码的标准是好的,因而通称他们叫作 “DBCS“(Double Byte Charecter Set 双字节字符集)。在DBCS系列标准里,最大的特色是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,所以他们写的程序为了支持中文处理,必需要注意字串里的每个字节的值,若是这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。那时候凡是受过加持,会编程的计算机僧侣们都要天天念下面这个咒语数百遍: “一个汉字算两个英文字符!一个汉字算两个英文字符……”
由于当时各个国家都像中国这样搞出一套本身的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码,连大陆和台湾这样只相隔了150海里,使用着同一种语言的兄弟地区,也分别采用了不一样的 DBCS 编码方案——当时的中国人想让电脑显示汉字,就必须装上一个”汉字系统”,专门用来处理汉字的显示、输入的问题,像是那个台湾的愚昧封建人士写的算命程序就必须加装另外一套支持 BIG5 编码的什么”倚天汉字系统”才能够用,装错了字符系统,显示就会乱了套!这怎么办?并且世界民族之林中还有那些一时用不上电脑的穷苦人民,他们的文字又怎么办? 真是计算机的巴比伦塔命题啊!
正在这时,大天使加百列及时出现了——一个叫 ISO(国际标谁化组织)的国际组织决定着手解决这个问题。他们采用的方法很简单:废了全部的地区性编码方案,从新搞一个包括了地球上全部文化、全部字母和符号 的编码!他们打算叫它”Universal Multiple-Octet Coded Character Set”,简称 UCS, 俗称 “unicode“。
unicode开始制订时,计算机的存储器容量极大地发展了,空间不再成为问题了。因而 ISO 就直接规定必须用两个字节,也就是16位来统一表示全部的字符,对于ASCII里的那些“半角”字符,unicode包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其余文化和语言的字符则所有从新统一编码。因为”半角”英文符号只须要用到低8位,因此其高8位永远是0,所以这种大气的方案在保存英文文本时会多浪费一倍的空间。
这时候,从旧社会里走过来的程序员开始发现一个奇怪的现象:他们的 strlen 函数靠不住了,一个汉字再也不是至关于两个字符了,而是一个!是的,从unicode开始,不管是半角的英文字母,仍是全角的汉字,它们都是统一的”一个字符“!同时,也都是统一的”两个字节“,请注意”字符”和”字节”两个术语的不一样,“字节”是一个8位的物理存贮单元,而“字符”则是一个文化相关的符号。在unicode中,一个字符就是两个字节。一个汉字算两个英文字符的时代已经快过去了。
unicode一样也不完美,这里就有两个的问题,一个是,如何才能区别unicode和ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,咱们已经知道,英文字母只用一个字节表示就够了,若是unicode统一规定,每一个符号用三个或四个字节表示,那么每一个英文字母前都必然有二到三个字节是0,这对于存储空间来讲是极大的浪费,文本文件的大小会所以大出二三倍,这是难以接受的。
unicode在很长一段时间内没法推广,直到互联网的出现,为解决unicode如何在网络上传输的问题,因而面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。UTF-8就是在互联网上使用最广的一种unicode的实现方式,这是为传输而设计的编码,并使编码无国界,这样就能够显示全世界上全部文化的字符了。UTF-8最大的一个特色,就是它是一种变长的编码方式。它可使用1~4个字节表示一个符号,根据不一样的符号而变化字节长度,当字符在ASCII码的范围时,就用一个字节表示,保留了ASCII字符一个字节的编码作为它的一部分,注意的是unicode一个中文字符占2个字节,而UTF-8一个中文字符占3个字节)。从unicode到utf-8并非直接的对应,而是要过一些算法和规则来转换。
Unicode符号范围 | UTF-8编码方式 |
---|---|
0000 0000-0000 007F | 0xxxxxxx |
0000 0080-0000 07FF | 110xxxxx 10xxxxxx |
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
最后简单总结一下:
中国人民经过对 ASCII 编码的中文扩充改造,产生了 GB2312 编码,能够表示6000多个经常使用汉字。
汉字实在是太多了,包括繁体和各类字符,因而产生了 GBK 编码,它包括了 GB2312 中的编码,同时扩充了不少。
中国是个多民族国家,各个民族几乎都有本身独立的语言系统,为了表示那些字符,继续把 GBK 编码扩充为 GB18030 编码。
每一个国家都像中国同样,把本身的语言编码,因而出现了各类各样的编码,若是你不安装相应的编码,就没法解释相应编码想表达的内容。
终于,有个叫 ISO 的组织看不下去了。他们一块儿创造了一种编码 UNICODE ,这种编码很是大,大到能够容纳世界上任何一个文字和标志。因此只要电脑上有 UNICODE 这种编码系统,不管是全球哪一种文字,只须要保存文件的时候,保存成 UNICODE 编码就能够被其余电脑正常解释。
UNICODE 在网络传输中,出现了两个标准 UTF-8 和 UTF-16,分别每次传输 8个位和 16个位。因而就会有人产生疑问,UTF-8 既然能保存那么多文字、符号,为何国内还有这么多使用 GBK 等编码的人?由于 UTF-8 等编码体积比较大,占电脑空间比较多,若是面向的使用人群绝大部分都是中国人,用 GBK 等编码也能够。
咱们知道,全部的信息最终都表示为一个二进制的字符串,每个二进制位(bit)有0和1两种状态。当咱们须要把字符'A'存入计算机时,应该对应哪一种状态呢,存储时,咱们能够将字符'A'用01000010(这个随便编的)二进制字符串表示,存入计算机;读取时,再将01000010还原成字符'A'。那么问题来了,存储时,字符'A'应该对应哪一串二进制数呢,是01000010?或者是10000000 11110101?说白了,就是须要一个规则。这个规则能够将字符映射到惟一一种状态(二进制字符串),这就是编码。而最先出现的编码规则就是ASCII编码,在ASCII编码规则中,字符'A'既不对应01000010,也不对应1000 0000 11110101,而是对应**01000001*(不要问为何,这是规则)。
这套编码规则是由美国定制,一共规定了128个字符的编码,好比空格"SPACE"是32(十进制)(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括 32个不能打印出来的控制符号),只占用了一个字节(8 bit)的后面7位,最前面的1位统一规定为0。总共才有128个字符编码,一个字节都没有用完,这好像彷佛有点太少了。因而乎,就开始压榨最高位,对其为1时也进行编码,利用最高位进行编码的方式就称为非ASCII编码,如ISO-8859-1编码。
这套编码规则由ISO组织制定。是在 ASCII 码基础上又制定了一些标准用来扩展ASCII编码,即 00000000(0) ~ 01111111(127) 与ASCII的编码同样,对 10000000(128) ~ 11111111(255)这一段进行了编码,如将字符**§编码成 10100111(167)。ISO-8859-1编码也是单字节编码,最多可以表示256**个字符。Latin1是ISO-8859-1的别名,有些环境下写做Latin-1。可是,即便可以表示256个字符,对中文而言,仍是太少了,一个字节确定不够,必须用多个字节表示。可是,因为是单字节编码,和计算机最基础的表示单位一致,因此不少时候,仍旧使用 ISO8859-1编码来表示。并且在不少协议上,默认使用该编码。好比,虽然"中文"两个字不存在ISO8859-1编码,以GB2312编码为例,应该是D6D0 CEC4两个字符,使用ISO8859-1编码的时候则将它拆开为4个字节来表示:D6D0 CEC4(事实上,在进行存储的时候,也是以字节为单位进行处理)。而若是是UTF编码,则是6个字节e4 b8 ad e6 96 87。很明显,这种表示方法还须要以另外一种编码为基础才能正确显示。而常见的中文编码方式有GB23十二、BIG五、GBK。
GB2312其对所收录字符进行了"分区"处理,共94个区,区从1(十进制)开始,一直到94(十进制),每区含有94个位,位从1(十进制)开始,一直到94(十进制),共8836(94 * 94)个码位,这种表示方式也称为区位码,GB2312是双字节编码,其中高字节表示区,低字节表示位。各区具体说明以下:
01-09区收录除汉字外的682个字符,有164个空位(9 * 94 - 682)。
10-15区为空白区,没有使用。
16-55区收录3755个一级汉字(简体),按拼音排序。
56-87区收录3008个二级汉字(简体),按部首/笔画排序。
88-94区为空白区,没有使用。
复制代码
那么根据区位码如何算出GBK2312编码呢?区位码的表示范围为0101 - 9494(包含了空的区位码)。点击这里,查看中GB2312编码区位码。以后只须要按照以下规则进行转化便可。
GB2312用两个字节编码,采用分区编码,总共编码的中文个数为6763(3755 + 3008)。这些汉字只是最经常使用的汉字,已经覆盖中国大陆99.75%的使用频率。可是,还有一些汉字在GB2312中没有被编码,如'镕'字,在GB2312中就没有被编码,这样就致使了问题,随之就出现了主流的GBK编码。在讲解GBK编码以前,咱们另外讲解一下BIG5编码。
BIG5采用双字节编码,使用两个字节来表示一个字符。高位字节使用了0x81-0xFE,低位字节使用了0x40-0x7E,及0xA1-0xFE。该编码是繁体中文字符集编码标准,共收录13060个中文字,其中有二字为重复编码,即“兀、兀”(A461及C94A)和“嗀、嗀”(DCD1及DDFC)。具体的分区以下:
8140-A0FE 保留给使用者自定义字符(造字区)
A140-A3BF 标点符号、希腊字母及特殊符号。其中在A259-A261,收录了度量衡单位用字:兙兛兞兝兡兣嗧瓩糎。
A3C0-A3FE 保留。此区没有开放做造字区用。
A440-C67E 经常使用汉字,先按笔划再按部首排序。
C6A1-F9DC 其它汉字。
F9DD-F9FE 制表符。
复制代码
点击这里,查看BIG5编码。注意,BIG5编码与GBK编码没有什么关系。
GBK编码扩展了GB2312,彻底兼容GB2312编码(如'李'字的GBK、GB2312编码均为C0EE),但其不兼容BIG5编码('長'字的BIG5编码为AAF8,GBK编码为E94C,'李'字的BIG5编码为A7F5 不等于C0EE),即若是使用GB2312编码,使用GBK解码是彻底正常的,可是若是使用BIG5编码,使用GBK解码,会出现乱码。相比于GB2312编码,GBK编码了更多汉字,如'镕'字。GBK编码依然采用双字节编码方案,其编码范围:8140-FEFE,剔除xx7F码位,共23940个码位。能表示 21003 个汉字。点击这里,查看GBK编码。点击这里,能够查询中文的其余编码。在GBK以后又出现了GB18030编码,可是没有造成主流,故不作介绍,至此,中文编码的问题已经讲解完成。那么问题又来了,大陆网民与在海峡两岸网民交流时,若都使用GBK编码,则没有问题,若一方使用GBK编码,一方使用BIG5编码,那么就会出现乱码问题,这是在海峡两岸网民交流,若是漂洋过海进行交流呢?那就更容易出现乱码问题,这时候咱们可能想,要是有一套全世界都通用的编码就行了,不要担忧,这样的编码确实是存在的,那就是Unicode。
有两个独立的, 创立单一字符集的尝试. 一个是国际标准化组织(ISO)的 ISO 10646 项目, 另外一个是由多语言软件制造商组成的协会组织的 Unicode 项目. 在1991年先后, 两个项目的参与者都认识到, 世界不须要两个不一样的单一字符集. 它们合并双方的工做成果, 并为创立一个单一编码表而协同工做. 两个项目仍都存在并独立地公布各自的标准, 但 Unicode 协会和 ISO/IEC JTC1/SC2 都赞成保持 Unicode 和 ISO 10646 标准的码表兼容, 并紧密地共同调整任何将来的扩展。
Unicode是指一张表,里面包含了可能出现的全部字符,每一个字符对应一个数字,这个数字称为码点(Code Point),如字符'H'的码点为72(十进制),字符'李'的码点为26446(十进制)。Unicode表包含了1114112个码点,即从000000(十六进制) - 10FFFF(十六进制)。地球上全部字符均可以在Unicode表中找到对应的惟一码点。点击这里,查询字符对应的码点。Unicode将码空间划分为17个平面,从00 - 10(十六进制,最高两位),即从0 - 16(十进制),每一个平面有65536个码点(2^16),其中最重要的是第一个Unicode平面(码位从0000 - FFFF),包含了最经常使用的字符,该平面被称为基本多语言平面(Basic Multilingual Plane),缩写为BMP,其余平面称为辅助平面(Supplementary Planes),在基本多文种平面內, 从D800到DFFF之间的码位区段是永久保留不映射到字符的, 所以UTF-16编码巧妙的利用了这保留下来的码位来对辅助平面内的字符进行编码,这点后面进行讲解。Unicode只是一个符号集,只规定的字符所对应的码点,并无指定如何存储,如何进行存储出现了不一样的编码方案,关于Unicode编码方案主要有两条主线:UCS和UTF。UTF主线由Unicode Consortium进行维护管理,UCS主线由ISO/IEC进行维护管理。
UCS全称为"Universal Character Set",在UCS中主要有UCS-2和UCS-4。
UCS-2是定长字节的,固定使用2个字节进行编码,从0000(十六进制)- FFFF(十六进制)的码位范围,对应第一个Unicode平面。采用BOM(Byte Order Mark)机制,该机制做用以下:
UCS-4是定长字节的,固定使用4个字节进行编码。也采用了BOM机制。
UTF全称为"Unicode Transformation Format",在UTF中主要有UTF-8,UTF-16和UTF-32。
UTF-8是一种变长编码方式,使用1-4个字节进行编码。UTF-8彻底兼容ASCII,对于ASCII中的字符,UTF-8采用的编码值跟ASCII彻底一致。UTF-8是Unicode一种具体的编码实现。UTF-8是在互联网上使用最广的一种Unicode的编码规则,由于这种编码有利于节约网络流量(由于变长编码,而非统一长度编码)。关于Unicode码点如何转化为UTF-8编码,能够参照以下规则:
Unicode符号范围 | UTF-8编码方式 |
---|---|
(十六进制) (十进制) | (二进制 |
0000 0000-0000 007F (0-127) | 0xxxxxxx |
0000 0080-0000 07FF (128-2047) | 110xxxxx 10xxxxxx |
0000 0800-0000 FFFF (2048-65535) | 1110xxxx 10xxxxxx 10xxxxxx |
001 0000-0010 FFFF (65536-1114111) | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
说明:字符'A'的Unicode码点为65(十进制),根据上表,在第一行范围,则字符'A'的UTF-8编码为01000001,中文字符'李'的Unicode码点为26446(十进制),二进制为01100111 01001110,十六进制为674E。根据上表,在第三行范围,则将'李'二进制代码从低位到高位依次填入x中,不足的填入0。获得UTF-8编码为11100110 10011101 10001110,即E69D8E(十六进制)。
由上述编码规则可知,0000 0000 - 0000 FFFF(第一行到第三行)为Unicode第一个平面(基本多语言平面),而0001 0000 - 10 FFFF(第四行)为Unicode其余平面(辅助平面)。在基本多语言平面对应了绝大多数经常使用的字符。对于大于65535(十进制)的码点,即在辅助平面上的码点,须要使用4个字节来进行UTF-8编码。
UTF-8是不定长的编码,使用一、二、三、4个字节编码,而UTF-16则只使用2或4个字节编码。UTF-16也是Unicode一种具体的编码实现。关于Unicode如何转化为UTf-16编码规则以下:
关于辅助平面的码点编码更详细解析以下:辅助平面码点被编码为一对16比特(四个字节)长的码元, 称之为代理对(surrogate pair), 第一部分称为高位代理(high surrogate)或前导代理(lead surrogates),码位范围为:D800-DBFF. 第二部分称为低位代理(low surrogate)或后尾代理(trail surrogates), 码位范围为:DC00-DFFF。注意,高位代理的码位从D800到DBFF,而低位代理的码位从DC00到DFFF,总共刚好为D800-DFFF,这部分码点在第一平面内是保留的,不映射到任何字符,因此UTF-16编码巧妙的利用了这点来进行码点在辅助平面内的4字节编码。
说明:字符'A'的Unicode码点为65(十进制),十六进制表示为41,在第一平面。根据规则,UTF-16采用2个字节进行编码。那么问题又来了,知道了采用两个字节编码,而且咱们也知道计算机是以字节为单位进行存储,这两个字节应该表示为00 41(十六进制)?或者是41 00(十六进制)呢?这就引出了一个问题,须要用到以前说起的BOM机制来解决。
表示为00 41意味着采用了大端序(Big endian),而表示为41 00意味着采用了小端序。那么计算机如何知道存储的字符信息采用了大端序仍是小端虚呢?这就须要加入一些控制信息,具体是采用大端序,则在文件前加入FE FF,采用小端序,则在文件前加入FF FE。这样,当计算开始读取时发现前两个字节为FE FF,就表示以后的信息采用的是小端序,反之,则是大端序。
Unicode转UTF-16规则流程图以下:
按照这个规则,咱们计算字符的UTF-16编码,咱们知道其码点为1016E,减去10000获得016E,扩展至0016E,进行分割,获得高十位为00 0000 0000,十六进制为0000,加上D800为D800;获得低十位为01 0110 1110,十六进制为016E,加上DC00为DD6E;综合获得D8 00 DD 6E。即UTF-16编码为D8 00 DD 6E(也可为D8 0 DD 6E)。
而对于UTF-32是使用4个字节表示,也采用BOM机制,能够类比UTF-16,这里再也不额外介绍。
从上面的分析知道,UCS-2采用的两个字节进行编码。在0000到FFFF的码位范围内,它和UTF-16基本一致,为何说基本一致,由于在UTF-16中从U+D800到U+DFFF的码位不对应于任何字符,而在使用UCS-2的时代,U+D800到U+DFFF内的值被占用。
UCS-2只能表示BMP内的码点(只采用2个字节),而UTF-16能够表示辅助平面内的码点(采用4个字节)。
咱们能够抽象的认为UTF-16可当作是UCS-2的父集。在没有辅助平面字符(surrogate code points)前,UTF-16与UCS-2所指的意思基本一致。但当引入辅助平面字符后,想要表示辅助平面字符时,就只能用UTF-16编码了。
在BMP上,UTF-16采用2个字节表示,而在辅助平面上,UTF-16采用的是4个字节表示。对于UCS-4,无论在哪一个平面都采用的是四个字节表示。
由于在UTF-8编码中,其自身已经带了控制信息,如1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx,其中1110就起到了控制做用,因此不须要额外的BOM机制。
终于能够说说emoji了。Emoji早先由日本企业发明,日文将其称为“絵文字”。后来随着智能手机的推广,全世界都在用,因而被Unicode收编了。截至Unicode 10.0,共有1144个emoji被收录。
这个小标题可能会有乱码或者显示一半的状况,这是有意为之。你们不妨来看一张图:
抛去格式不太友好以外,有一处特别的地方是emoji的两片飘叶显示不对。实际上除了微信,这个emoji在几乎哪都不显示。这就奇怪了,我特意把两个软件中的飘叶都复制出来,结果发现他们压根连码点都不一样:
emmmm...同一个emoji怎么会变码点呢?emoji的特殊规则?没据说啊。。
几经搜索,我终于找到了缘由。原来iPhone中使用的emoji最先是软银的一套编码,飘叶在这套编码中的码点正是U+E447。这个码点位于用户可自定义的私有码位区域,所以现今以Unicode的角度来看这个字符,它是没有确切的字形的。如今绝大部分软件也都再也不支持这套老emoji了。这个emoji来自于一位微信用户的昵称,刚好微信还支持这套老emoji,因而可以显示出来。
若是emoji就这点玩法的话,也不至于写这么一章了。实际上emoji比我最初的认识还要复杂不少。Unicode的emoji除了可使用你们平时见到的彩色来展现,还能够用单色来展现,以适应一些很是简单的显示设备。
怎么作呢?规则就是在普通的emoji码点以后,紧跟一个用来表示颜色版本的“变幻符”,这个变幻符有两个取值:VS15(U+FE0E)和VS16(U+FE0F)。其中VS15表示强制使用单色版,而VS16则表示强制使用彩色版。若是没有变幻符呢,每一个emoji可使用本身默认的展现。
举个例子来讲,U+26A0这个emoji能够有两种样子:
各位还记得Unicode当中的“组合字”么,殊途同归。
想必你们已经见过了,如今每一个人类emoji都有不少种肤色版本。Unicode从8.0开始,为全部展现人或人体部位的emoji都增长了肤色控制。
在普通人物的emoji码点以后若是跟上一个肤色码点,那么这个emoji就会采用相应的肤色。举个例子:
👃🏿 = 👃(U+1F443) + 🏿(U+1F3FF)
实际上无论emoji中的人是否露出皮肤,均可以这样组合,画emoji的人能够去抉择如何去表现。不管如何,如今用emoji已经能够这么干了:
🎅🏿???
上面的emoji组合,都是同一个emoji内部的事情,然而实际上多个独立的emoji也能够进行组合。
在Unicode中,存在一个特殊的码点,被称为零宽链接符(ZWJ),其码点为U+200D。这个零宽链接符在平时是不会显示出来的,否则也不会叫零宽链接符了。以前网上有人贴出“空字符串”几百个字节,就是用它了。
零宽链接符在emoji中的做用,就是能够i将多个emoji组合成一个更大的emoji。你们平时在网上看到的全家福emoji,正是这样作出来的:
👨❤️💋👨 = 👨(U+1F468) + ZWJ(U+200D) + ❤(U+2764) + ZWJ(U+200D) + VS16(U+FE0F) + ZWJ(U+200D) +💋(U+1F48B)+ ZWJ(U+200D) +👨(U+1F468)
能够说是很解耦了。。
固然也不是全部的软件如今都支持ZWJ这种玩法,对于不支持的软件,全家福就会被打散。
那么做为一个前端,JavaScript对Unicode的支持是怎样的呢?事实是,JavaScript的字符串使用UTF-16来存储字符。在我印象里,有这么几个函数与Unicode关系最大:
String.prototype.charAt() //返回指定位置的字符 String.prototype.charCodeAt() //返回指定位置的UTF-16编码 String.prototype.codePointAt() //返回指定位置的Unicode码点 String.length //返回对象中字符串所占的UTF-16单元数量
干说没用,来考虑一下下面这段代码:
// U+1F4A9
const str = '💩';
console.log(str.charCodeAt(0));
console.log(str.charAt(0));
console.log(str.codePointAt(0));
复制代码
这是一坨屎无误,第一行log的状况是:
console.log(str.charCodeAt(0));
// 55357
// 0xD83D
复制代码
嗯?好像和码点对不上?答案是这样的
'💩' === '\u{1F4A9}'
'💩' === '\uD83D\uDCA9'
复制代码
哦,原来这坨屎已经超出了UTF-16最初支持的2字节表示,所以须要使用代理对来表示,这样一来就不是直接给出码点了。
这样的话,第二行log输出也就很好理解了:
console.log(str.charAt(0));
// ‘?'
复制代码
charAt比较简单,只是单纯地将UTF-16字符串按下标返回对应的字符,这里会打出一个代理对的一半,因此显示不出来。也就是说chartAt是不会完整吐出一个须要代理对的字来的。
第三个log给出了你可能最想要的:
console.log(str.codePointAt(0));
// 128169
// 0x1F4A9
复制代码
这是真正的Unicode码点无误了。那么console.log(str.length);的话,会输出什么呢?因为这个属性返回的是UTF-16单元的数量,而这坨屎须要2个UTF-16单元,所以其输出会是2。这可不是字节数哦。
那么,既然str.codePointAt(0)可以返回出整坨屎的Unicode码点,那str.codePointAt(1)会返回什么呢?ES标准说了,若是这个下标不是代理对的开头,那么只返回指向的UTF-16单元,也就是说:
console.log(str.codePointAt(1));
// 56489
// 0xDCA9
复制代码
最后说一下for循环的区别:
因此只有for...of是真正理解Unicode的。你们用for来循环的时候,可要当心了,不然一不当心就会把代理对给拆开。
计算机的历史只有短短不到100年的时间,而互联网则只有不到30年。由于历史很短,不少时候咱们会产生一种假象,那就是计算机的历史好像是笔直的,一切设计都很合理、恰到好处,只需理解一下高抽象层次的概念和原理便可。而事实上则刚好相反,计算机世界的历史崎岖不平,充满了错误和由于错误而颠簸的设计,这里面隐藏了大量的细节。有时咱们伪装本身已经对程序了如指掌,“啊,编码嘛,不就是映射一下嘛;哦,HTTP协议嘛,很简单啊,就是个抽象层而已啊”,伪装本身是高级程序员,所以好像能够忽略这些细节。实际呢,处处都是坑!
细节就是魔鬼,即便在看起来并不复杂的字符编码上也是如此。若是你忽视了这些,就只有用户来替你承担了。