Www.KL126.Com - 坤龙教育资源网

Javascript有个Unicode的天坑

作者:小白 来源:未知 日期:2016-12-10 18:02:41 人气: 标签:unicode
导读:最近笔者在项目中遇到了emoji表情的处理,期间发现js处理多字节字符时会有较多坑,记录一下与各位分享。本文涉及知识点:Unicode(BMP/SP)UTF-8UTF-16UTF-32UCS-2…

  最近笔者在项目中遇到了emoji表情的处理,期间发现js处理多字节字符时会有较多坑,记录一下与各位分享。

  本文涉及知识点:

  Unicode(BMP/SP)

  UTF-8UTF-16UTF-32UCS-2

  javascript字符处理

  Unicode是目前绝大多数程序使用的字符编码,定义也很简单,用一个码点(codepoint)映射一个字符。码点值的范围是从U+0000到U+10FFFF,可以表示超过110万个符号。下面是一些符号与它们的码点

  对于每个码点,Unicode还会配上一小段文字说明,可以在查到,比如:hankey:的码点说明

  Unicode最前面的65536个字符位,称为基本平面(BMP-—BasicMultilingualPlane),它的码点范围是从U+0000到U+FFFF。最常见的字符都放在这个平面,这是Unicode最先定义和公布的一个平面。

  剩下的字符都放在补充平面(SupplementaryPlane),码点范围从U+010000一直到U+10FFFF,共16个。

  UTF(Unicodetransformationformat)Unicode转换格式,是服务于Unicode的,用于将一个Unicode码点转换为特定的字节序列。常见的UTF有

  UTF-8可变字节序列,用1到4个字节表示一个码点

  UTF-16可变字节序列,用2或4个字节表示一个码点

  UTF-32固定字节序列,用4个字节表示一个码点

  UTF-8

  对ASCⅡ编码是兼容的,都是一个字节,超过U+07FF的部分则用了复杂的转换方式来映射Unicode,具体不再详述。

  UTF-16对于BMP的码点,采用2个字节进行编码,而BMP之外的码点,用4个字节组成代理对(surrogatepair)来表示。其中前两个字节范围是U+D800到U+DBFF,后两个字节范围是U+DC00到U+DFFF,通过以下公式完成映射(H:高字节L:低字节c:码点)

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

  L=(c–0x10000)%0x400+0xDC00

  比如:hankey:用UTF-16表示就是”/uD83D/uDCA9″

  UCS(UniversalCharacterSet)通用字符集,是一个ISO标准,目前与Unicode可以说是等价的。

  相对于UTF,UCS也有自己的转换方法(编码)。如

  UCS-2用2个字节表示BMP的码点

  UCS-4用4个字节表示码点

  UCS-2是一个过时的编码方式,因为它只能编码基本平面(BMP)的码点,在BMP的编码上,与UTF-16是一致的,所以可以认为是UTF-16的一个子集。

  UCS-4则与UTF-32等价,都是用4个字节来编码Unicode。

  辣莫,js到底是用的啥编码呢?答案是UCS-2。咦,刚刚不是说UCS-2过时了吗?首先看下年表

  1990UCS-2诞生

  1995.5JavaScript诞生

  1996.7UTF-16诞生

  也就是说,BrendanEich在写JS的时候,UTF-16还没问世,所以只能用UCS-2的方式来处理字符,也因此留下了隐患。

  先看一个简单的例子:

  ”/uD83D/uDCA9″===“:hankey:”

  true

  ”:hankey:”.length

  2

  因为”:hankey:”在JS的编码是”/uD83D/uDCA9″,而JS认为每16位(2字节)即表示一个字符,所以一坨大便是占2个字符的。我们经常用length来判断字符串长度,那产品不干了呀,说好可以输入10个字,为毛输了5个emoji就不给输入了?

  怎么破?可以用万能的正则匹配

  varregexAstralSymbols=/[/uD800-/uDBFF][/uDC00-/uDFFF]/g;//匹配UTF-16的代理对 functioncountSymbols(string){returnstring//把代理对改为一个BMP的字符..replace(regexAstralSymbols,_)//…这时候取长度就妥妥的啦..length;}countSymbols(:hankey:);//1

  js里怎么反转(reverse)字符串?相信有些同学已经想到了一个极简的方案

  functionreverse(str){    returnstr.split().reverse().join();}

  js虽没有直接的反转字符串的API,但是数组有啊,转数组反转之后再转回字符串,嘿嘿嘿,是不是很机智?这时候Unicode大爷又出来打脸了:你们呐,sometimesnaive!

  拿刚才的函数反转带有:hankey:的字符串试试

  reverse(这是一坨:hankey:)��坨一是这

  �的Unicode码点是+UFFFD,通常用来表示Unicode转换时无法识别的字符(也就是乱码)

  当:hankey:(/uD83D/uDCA9)通过上述方法反转时,变成/uDCA9/uD83D,不是一个合法的代理对(高低字节范围不同),同时,Unicode规定代理对范围内的码点不能单独出现,所以js只能用�表示了。

  怎么破?

  事实上这个API是支持俩参数的,分别是代理对的高低字节。所以需要通过公式计算出对应的高低字节

  String.fromCharCode(0xD83D,0xDCA9):hankey://U+1F4A9:hankey:.charCodeAt(0)0xD83D

  一个字,蛋疼!

  怎么破?ES6大法好。

  String.fromCodePoint(0x1F4A9):hankey://U+1F4A9:hankey:.codePointAt(0)0x1F4A9

  思考一下,什么正则表达式可以表示任何Unicode字符?显然.

  是不够的,因为它不能匹配辅助平面字符或者换行符。那么用/s/S呢?

  ``/^[/s/S]$/.test(:hankey:)false```

  怀疑人生了~~正确的匹配任意Unicode字符的正则如下:

  /[/0-/uD7FF/uE000-/uFFFF][/uD800-/uDBFF][/uDC00-/uDFFF][/uD800-/uDBFF](?![/uDC00-/uDFFF])(?:[^/uD800-/uDBFF]^)[/uDC00-/uDFFF]/.test(:hankey:)//wtftrue

  从上面的例子中可以看出,ES6已经在很努力地填坑了。对于Unicode字符,ES6支持新的表示方法

  /u{1F4A9}

  加上花括号后,可以把码点直接填进去来表示,而不用去计算代理对。再补充2点:

  1.为了向后兼容,字符串的length属性还是用双字节判断的,所以要用Array.from(str).length。

  2.遍历字符串的时候,可以用for(letsofstr){}

  参考资料:

  Unicode与JavaScript详解

  JavaScripthasaUnicodeproblem

推荐: