资讯专栏INFORMATION COLUMN

细说Unicode(二) Unicode与JavaScript的纠葛

Achilles / 3466人阅读

摘要:受到这个的影响,中的字符操作函数某些情况无法返回正确的结果。的码点,还有另外一种表示方法,称为进制转义序列。这与我们的认知有点不同,我们通常认为一个表情符号也是一个字符,长度为。而如果通过来判断字符串长度显然是不够准确的。

大家对上一篇文章中提到的UCS编码可能比较陌生。殊不知这就是JavaScript采用的编码方法。

既然Unicode已经统一了天下,为什么JavaScript不采用UTF的编码方法呢?原因很简单,因为JavaScript诞生的时候UTF-8还尚未成熟,UTF-16更是到后面才出现,而此时UCS已经先行一步地完成了UCS-2。所以JavaScript采用了比UTF更早的UCS。也就是UCS-2。(记住只是编码方法,实际上字符集还是Unicode字符集)

UCS-2 与 UTF-16

从命名上看,我们很容易猜出UCS-2占用2个字节。而UTF-16占用16位,也是2个字节,那他们的编码方式有什么不同呢?
对于2个字节的码点,UCS-2和UTF-16是没有什么区别的。在基本平面上(2^16),UTF-16沿用了UCS-2的编码,另外在辅助平面上,UTF-16还定义了4个字节的表示方法。简单来说,UTF-16可看成是UCS-2的父集。在没有辅助平面字符前,UTF-16与UCS-2所指的是同一的意思。但当引入辅助平面字符后,就称为UTF-16了。

由于JavaScript只能处理UCS-2编码,造成所有字符都是2个字节,如果是4个字节的字符,会被当做两个双字节的字符处理。受到这个的影响,JavaScript中的字符操作函数某些情况无法返回正确的结果。

对于两个字节的字符,js能够根据码点直接输出对应字符。例如小写字母"a"的Unicode编码就是U+0061。

U+0000 - U+00FF的码点,还有另外一种表示方法,称为16进制转义序列。用"x"开头,后面跟两位的16进制符。

大于两个字符的码点,JavaScript就有点力不从心了。例如字符

这个符号的字符码点为 "U+1F4A9", 控制台的输出结果是这样的

这显然不是正确的结果,那么这个符号是怎么产生的呢?

由于UCS-2每次只能读取两个字节,所以 "U+1F4A9"被解读为U+1F4A 和 9, 查阅Unicode映射表U+1F4A 对应的是希腊语的扩展,就是是符号0加一点。

剩下的9则被识别为普通的字符串符号"9"输出了。

既然JavaScript无法处理大于两个字节的符号,那对于互联网上成千上万的复杂字符和表情,岂不是束手无策?

非也!

我们在控制台输出这个码点:”uD83DuDCA9″

神奇的事情发生了,”uD83DuDCA9″竟然也能输出符号。

如果我们多带带输出这两个码点,看会输出什么字符:

两个字符多带带输出都是乱码,Unicode无法识别对应的字符。再次查阅映射表。

发现这两个码点分别落在了UTF-16的高半位和低半位。

原来UTF-16碰到第一个双字节码点在D800-DBFF之间时,代码不会直接读取符号,而是将其存储为高半区,再往下读取两个字节的低半区,合在一起再输出符号。而这也是UCS-2的处理方式。

那么 "U+1F4A9"怎么转化为高低位”uD83DuDCA9"呢,下面是转换公式:

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

L = (0x1F4A9-0x10000) % 0x400+0xDC00 = 0xDCA9

既然我们已经能够在JavaScript中输出辅助平面的字符了,那不是万事大吉了吗?

常见问题

考虑一个常用的前端场景——输入框,通常会规定最大输入字数。尝试输出上面的符号长度, 发现长度是2。

这与我们的认知有点不同,我们通常认为一个表情符号也是一个字符,长度为1。而如果通过"xxx".length 来判断字符串长度显然是不够准确的。这个问题在ES6中能迎刃而解:

ES6中通过Array.from能准确读取字符长度

然而Array.from不是完美的,在某些场景下也无法满足需求,况且还存在ES6的浏览器兼容性问题。

在ES5中,我们通过正则的判断,也能得到Array.from的效果,而且扩展性更高:

var regexAstralSymbols = /[uD800-uDBFF][uDC00-uDFFF]/g;

function countSymbols(string) {
    return string
        // 替换掉辅助平面的连字符
        .replace(regexAstralSymbols, "_")
        .length;
}

countSymbols("uD835uDC00"); //1

另外,JavaScript也提供了从码点到字符的转换函数。

//这里直接输入进制数0x0061或97,而不是字符串
String.fromCharCode(0x0061); //a
//输出为10进制数
"a".charCodeAt(0);//97 (16进制0x0061)

而对于附加平面的符号,JavaScript又要跪了, 直接输出低位 U+F4A9的字符,而该字符位于Unicode的私用区,未定义,所以输出""。

String.fromCharCode(0x1F4A9);//""

同样的,我们将符号U+1F4A9变为高地位输入,就能成功输出符号

对于fromCharCode和charCodeAr这两个方法,ES6 也提供了新的接口,对应fromCodePoint和codePointAt,问题得到解决:

在处理字符串逆转,正则的匹配上,附加字符都会有问题,要处理这些问题,只有一条准则,就是要对附加码点做特殊处理。在ES6还没全面支持的情况下,只能通过定义各种hack方法来解决。

关于Unicode跟JavaScript的纠葛就讲到这,乱码问题让人费解,但是只要了解了基本原理,问题往往就能迎刃而解。

参考文章:
https://zh.wikipedia.org/wiki
https://mathiasbynens.be/note...
http://www.ruanyifeng.com/blo...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86749.html

相关文章

  • 细说Unicode(一) Unicode初认识

    摘要:所以中国人自己创造了一种字符编码,每个汉字和符号用两个字节来表示。第一个字节称为高位字节,第二个字节称为低位字节。而目前为止我们使用最广泛的中文编码还是。 网站开发中经常会被乱码问题困扰。知道文件编码错误会导致乱码,但对其中的原理却知之甚少。偶然从某篇文章了解了Unicode,发现从这条线出发也牵引出了一系列缺失的知识点。通过研读文章,基本了解了一些以前不明白的问题,所以整理了几篇,从...

    loostudy 评论0 收藏0
  • 细说Unicode(三) Unicode 番外之附加字符

    摘要:在各种论坛上,经常会看到一些奇怪的字符,它们的内容会超出显示范围,举个例子常见的还有一些有泰文字符组成的。第一种是对字符串文字区域设置最大高度,超出的部分自动隐藏。将附加字符进行过滤,这种方法在某种程度上会误杀一些需要正常显示的附加符号。 在各种论坛上,经常会看到一些奇怪的字符,它们的内容会超出显示范围, 举个例子: Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘...

    qujian 评论0 收藏0
  • 有关javascript强制转换不得不说故事

    摘要:我们首先了解一下中有关类型转换的知识。新增类型抛出异常从列表可以明显看到少了一个类型转换为的规则。这里要强调一点第二个表达式没有涉及到强制类型转换。如果文中有错误或者有某些强制转换的情形没有涉及到请及时留言告知,我会修改并补充进去。 javascript是一门非常奇特的语言,它有时候奇特的会让人怀疑人生。比如让我们看一下下面的一些奇葩例子: false == 0 ...

    xcold 评论0 收藏0
  • UnicodeJavaScript详解

    摘要:本文大部分内容转自阮一峰前辈的文章,更新了部分内容并加入了部分自己的理解。字符串处理函数新增了几个专门处理字节码点的函数。参考链接阮一峰与详解辅助平面入门 本文大部分内容转自 阮一峰前辈的文章,更新了部分内容并加入了部分自己的理解。 Unicode是什么? Unicode源于一个很简单的想法:将全世界所有的字符包含在一个集合里,计算机只要支持这一个字符集,就能显示所有的字符,再也不会有...

    econi 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<