那些关于字符编码的前言看到ES6给String扩展了很多新特性,字符串操作更加友好,比如"u{1f914}",codePointAt(),String.fromCodePoint().里面涉及到很多字符编码的知识。为了更好地理解这些新特性,本文对字符编码相关知识进行了更全面的回顾和总结。以下内容包括:字符集与字符编码的关系及编码规则、JS字符编码、HTML转义序列。首先回顾一下前言中关于位和字节(小b和大B)最基本的知识。1bit=1binarybit=0or18bit=80or1(2^8=256combinations)=1byteByte值得一提的是,在计算带宽大小(bps)时,注意以bit为单位。1、字符集和字符编码Unicode、ASCII、GB2312、GBK、BIG5都属于字符集。每个字符集包含不同类型和数量的字符。每个字符都有自己的编号作为唯一标识符。.那么它们是如何编号的(以下简称码点)呢?不同的字符集有不同的方案。对于ASCII、GB2312、GBK、BIG5,实行“垄断”政策,即只允许使用其指定的编码方案,也可以认为它既是字符集又是字符编码。但是Unicode实行“百家争鸣”的方针,提供了UTF-8/UTF-16/UTF-32几种可供选择的字符编码方案,所以此时Unicode只是一个字符集,而UTF-X是字符编码。各个字符集的具体编码方案可以看这里。为此,经常听到ASCII编码、GB2312编码,甚至Unicode编码。这种名称很容易混淆字符集和字符编码的关系。明确了字符集和字符编码的关系后,我们就可以知道,如果一个字符要从UTF-8编码转换为GBK编码,必须先将其unicode码位转换为GBK码位,然后再进行GBK编码。下面我们主要看ASCII和Unicode这两种字符集(编码)。2、ASCII字符集和编码ASCII是最古老、最原始的字符集和编码,主要满足英文字符的需要。毕竟电脑是从老美人中诞生的。每个字符存储在一个字节(8bit)中,一共定义了128个字符。前32个字符是非打印控制字符(回车、换行等)。虽然一个字节最多可以定义256个字符,但ASCII只使用一个字节的后7位,第一部分统一为0。空格“SPACE”码位:十进制32,十六进制20,二进制00100000大写字母A码位:十进制65,十六进制41,二进制01000001ExtendedASCIIASCII只有128个字符,其他语言不够,怎么办?别忘了ASCII只用到最后7位,利用最高的空闲位,可以扩展到256个字符,成为扩展的ASCII码(EASCII)。因此,0-127码位所代表的字符是相同的,唯一不同的是128-255码位。由于只能扩展128个字符,而且不同语言的字符不同,为了满足不同地区更多字符的需要,扩展字符的含义不能相同。这里会有如ASCII码表“阿拉伯字符(ASMO-708)码”扩展ASCII、“泰语(Windows)码”扩展ASCII。对于中文来说,每字节256个字符显然是不够的,所以中文只能单独开发GB2312、GBK、GB18030、BIG5等字符集。您可以在此处阅读有关GBXXX编码的信息。看到这里的ASCII码表,有种你圈子真乱的感觉。每个国家都有自己的一套字符集和编码,不利于交流。直到Unicode出现。3.UnicodeUnicode解决了各国各有一套的问题,收录了世界上所有的符号。它提供了一个独特的代码点,无论什么平台,无论什么程序,无论什么语言。codepoint码位范围为0x0-0x10FFFF,分为17个Planes。每个Plane有65536个字符,可以容纳:17*(16*16*16*16)=1114112个字符。第一个平面称为基本多语言平面(BMP)。其他平面称为补充平面(SP)或星体平面。在BMP中,U+D800到U+DFFF的码位块是永久保留的,不映射到Unicode字符。后面介绍的UTF-16使用0xD800-0xDFFF段的保留码位来编码辅助平面字符的码位。如前所述,Unicode只是一个字符集。具体码位如何存储可以选择UTF-8、UTF-16或UTF-32编码。这里插入一个名词:codeunits编码单元是各种编码方式的基本单位,长度单位是bit。一个代码点可能只需要一个代码单元,也可能需要多个代码单元。UTF-x等编码方式中的数字,实际上是指定了这种编码方式下的码元长度。比如UTF-8的编码单元长度是8bit……当一个码点太大,一个编码单元长度无法存储时,就需要分解成两个或多个编码单元存储。例如0x10437码位UTF-16会被分解为D801DC37两个码位(每个码位为16bit),UTF-8会被分解为f09090b7四个码位(每个码位为8bit)).码表Unicode编码转换器1。UTF-8是Internet上使用最广泛的Unicode实现,它是一种变长编码方式。一个字符可以用1~4个字节来存储,字节长度根据不同的符号而不同。UTF-8编码规则Unicode码位范围UTF-8编码方式(二进制)UTF-8编码要求大小规则备注该符号的unicode编码。英文字母、UTF-8码和ASCII码相同U+00000080-U+000007FF110xxxxx10xxxxxx2byte第一个字节的前两位为1,第三位为0;后面字节的前两位都设置为10U+00000800-U+0000FFFF1110xxxx10xxxxxx10xxxxxx3byte第一个字节的前三位为1,第四位为0;后面字节的前两位都设置为10个汉字(U+4E00-U+9FA5)UTF-8编码为3个字节U+00010000-U+0010FFFF11110xxx10xxxxxx10xxxxxx10xxxxxx4byte的前四位第一个字节为1,第五位为0;后面字节的前两位相同设置为102.UTF-162或4字节存储一个字符2字节:代码段(BMP)从0x0-0xFFFF,编码值与unicode对应的代码点一致4个字节(两个doubleByte):从0x10000-0x10FFFF(SP,已经超出BMP平面)的码点,会按照规则被编码成一对16bit长的码单元:比如0x10437码点会被编码成D801DC37,它们被称为代理对(surrogatepair)4字节代理对的原理从上面D801DC37的例子可以发现,这两个码点都在BMP平面的码点范围内,都属于U+D800到U+DFFF码段。没错,通过一系列的规则,将BMP平面之外的码点(U+10437)转换为属于BMP平面的两个码点——U+D800和U+DFFF码段之间(U+D801和U+D801)+DC37)。粗略的解释一下,SP平面的codepoint范围是从U+10000到U+10FFFF,一共FFFFF,即2^20=1,048,576,需要20个bits来表示。如果用两个双字节长码位的序列表示,第一个码位(称为高位代理)应容纳上述20位中的前10位,第二个码位(称为低位代理))应容纳上述20位数字中的最后10位数字。前后分别需要2^10=1024个码位表示。BMP平面的U+D800到U+DFFF码段刚好有2048个码点,足以满足高阶代理和低阶代理的需求。因此需要将U+D800-U+DFFF分成两段,一段为高阶代理U+D800的初始值:U+D800和U+DBFF之间的保留码位用于前导代理人。ValueU+DC00:U+DC00和U+DFFF之间的保留码位,用于两段尾随代理之间的2^10=1024的区间,刚好满足前后10位,如U+10437编码(?)0x10437减去0x10000,结果为0x00437,二进制为00000000010000110111对其高10位值和低10位值进行分区(使用二进制):0000000001和0000110111将0xD800添加到上限值以形成高位:0xD800+0x0001=0xD801。将0xDC00添加到较低的值以形成较低的位:0xDC00+0x0037=0xDC37。获取其UTF-16编码作为D801DC37jsimplementfunctionfindSurrogatesPair(codePoint){varoffset=codePoint-0x10000;varlead=0xd800+(offset>>10);vartail=0xdc00+(offset&0x3ff);//和1023位并将前十位设置为0return[lead.toString(16)+tail.toString(16)];}//examplevarstr="?";varcp=str.codePointAt(0);utf16编码(cp);//返回["d83e","dd14"]console.log('\ud83e\udd14');//?UTF-16是UCS-2在没有辅助平面字符之前的超集,UTF-16与UCS-2所指的含义相同。但是当引入辅助平面字符时,它就被称为UTF-16。也就是说,UCS-2编码不能支持UTF-16中超过2个字节的字符集。4.JSCharacterEncoding阮老师在ES6教程字符串扩展第一小节的字符的unicode表示法中提到:...用这种表示法,JavaScript有6种方式来表示一个字符。'\z'==='z'//true'\172'==='z'//true'\x7A'==='z'//true'\u007A'==='z'//true'\u{7A}'==='z'//true这里的\u007A貌似JS是用UTF-16编码的,但是加载JS时指定的charset一般好像是UTF-8或者GBK?JS到底是用什么编码的?这个问题我一直有点疑惑,但其实JS的编码问题应该分为两个不同的部分:内部:JS引擎是如何解析的?External:浏览器使用什么编码来解析JS脚本?1.Internal:JS引擎解析源码引擎会把所有的源码都看成是一串UTF16符号,也就是内部用UTF-16编码。varf\u006F\u006F=123;console.log(foo);//123console.log(\u0066\u006F\u006F);//123varfoo='12\u0033';//123//中文varteng='123';控制台日志(\u817e);//123//4字节字符varbar='?';console.log('\uD842\uDFB7');//'?'上面的例子可以看出,无论是字符串还是变量,无论是BMP还是SP上的字符,都可以用UTF-16编码单元来表示。ES6中的大括号符号怎么样?好像不需要UTF-16编码,把码点用花括号括起来就可以了。'\u{20BB7}'==='\uD842\uDFB7'//甚至全等其实只是句法糖,这个句法糖很好,ES6内部把大括号内的码位编码成UTF-16,不需要自己转换成代理对。另外上面还有三种奇怪的表示'\z'==='z'//true'\172'==='z'//trueoctal'\x7A'==='z'//true16进制\z其实是用来转义特殊字符的,比如rnt"'等,非特殊意义的字符\z等于自己的八进制表示法,反斜杠后取值范围是0-377(0-255indecimal),官方的说法是用十六进制表示Latin-1编码的字符,取值范围是00-FF,上面八进制表示的目的和fans.prototype.length的字符串是一样的其实理解了上面的知识,就不难理解字符串的长度了,对于JS引擎来说,所有的字符串都是一系列的UTF-16编码单元,而长度指的就是编码单元(也可以是理解为两个字节等于1个长度),不是字符个数。当一个字符用UTF-16编码4个字节时,一个字符的长度为2。但中文的长度始终为1。这是因为编码中文的point范围是U+4E00-U+9FA5,都在BMP平面,UTF-16编码只需要2个字节。看例子~//还是取Theword'?'==='\uD842\uDFB7';'\uD842\uDFB7'==='\u{20BB7}';varfoo='?';varbar='\uD842\uDFB7';varbaz='\u{20BB7}';console.log(foo.length,bar.length,baz.length);//222如果我们需要得到“正确”的长度值,我们应该怎么做呢?ES6轻松解决:如果Array.from(str).length不是ES6也不要气馁,只要识别相邻的两个代码元素是否形成代理对关系即可(上面解释了原理~),如果是则进行处理作为一个整体。函数getRealLen(str){varreg=/[\ud800-\udbff][\udc00-\udfff]/g;///[高代理][低代理]/greturnstr.replace(reg,'i').length;}getRealLen('?');//1getRealLen('??????');//52.外部:浏览器解析JS脚本我们可以用不同的编码方式保存源码,但是如果浏览器解码方案和保存源码时的编码方案不一样,就会造成乱码。当浏览器加载一个tagBOMBOM(U+FEFF)不同编码下的编码方式0000FEFFUTF-32,big-endianFFFE0000UTF-32,little-endianFEFFUTF-16,big-endianFFFEUTF-16,little-endianEFBBBFUTF-85,HTML转义字符最后总结一下前端经常看到的转义字符如,中。它们是HTML、XML等类SGML语言中的转义序列(escapesequences),浏览器在遇到它们时会自动渲染成文本。它有三种表达方式:dddd;后跟一个十进制数——称为实体数hhhh;后跟一个十六进制数——称为实体号&name;&后跟一个预定义的实体名称-它被称为实体名称。前两种方式的数字是目标字符的unicode码位,与文档编码无关。HTML特殊转义字符列表字符实体实体编号实体名称中文中国或中国none&&&实体编号可以使用ES6中的String.fromCodePoint(codePoint)String.fromCodePoint(20013);//在String.fromCodePoint(0x4e2d);//在domvar中或与domvar$dom=$('
