多字节字符
在实现字符串脱敏需求时,遇到一个很有意思的问题:当我用String.prototype.subStr来获取字符串切片时,JavaScript并不能正确的识别emoji的长度。于是debug出以下规律。
1 | var s = 'a'; |
好家伙,完全没想到还能有这种问题。。
找了时间搜索一番,总结出以下知识点
本文的所有问题几乎都能在阮一峰的《Unicode与JavaScript详解》里找到答案
什么是Unicode、UTF-x
现在Unicode指的是一套字符集,UTF-8是Unicode字符集的一种实现方式。Unicode字符集规定了每个字符的码点(如U+597D = 好
),但到底用什么样的字节序表示这个码点,就是UTF-x干的事。
如UTF-32则完全对应Unicode编码。固定占用四个字节。四个字节一个码点。
UTF-8则是变长的编码方法,字符长度从1个字节到4个字节不等
。越是常用的字符,字节越短(哈夫曼编码?)
UTF-16则是介于两者之间。Unicode中的”基本平面“占用2个字符。”辅助平面“占用4个字节。
基本平面、辅助平面的概念去阮老师博客里看。。
JavaScript用的什么编码
JavaScript用的叫UCS-2编码。因为历史原因。JavaScript诞生时,Unicode还未出现。但好在UCS-2后来合并到UTF-16里。可以简单的理解,JavaScript用的是UTF-16编码
所以可以简单的理解
很常见的中文、英文是写在基本平面,占用2个字节。而emoji是辅助平面内。占用4个字节。。
那么’ผู้’.length为啥为3?
这个应该要扯到Emoji Sequence
,这篇文章扯到展开操作符:一家人就这么被拆散了。
暂时不了解。暂时不展开讲。
打印出来的\uD83D代表什么
这是是码点表示法
,写法是”反斜杠+u+码点“
在这里的”码点“是十六进制,与JavaScript中十六进制数字0xD83D
一样。所以容易误解
几个常用的字符操作方法
String.fromCharCode
返回由指定的 UTF-16 代码单元序列创建的字符串
如果是双字节字符的第一个字符的画,则直接返回码点。否则可以返回对应的字符
String.fromCharCode('0xD83D') // '\uD83D'
String.fromCharCode(22909) // '好'
String.fromCharCode('0x597D') // '好'
可以接收多个参数
String.fromCharCode(22909, 22909); // ‘好好’
String.fromCodePoint
ES6加上的,为了解决双字节问题。基本签名同String.fromCharCode,多的功能是支持大于0xFFFF的码点。也就是直接输入双字节字符的码点。
String.fromCodePoint('0x1f601') // '😁'
String.fromCharCode('0x1f601') // ''
String.fromCharCode('0xf601') // ''
String.prototype.charAt
只是返回字符而已。效果同直接下标访问
var s1 = '😀'
s1.charAt(0) === s1[0]
String.prototype.CharCodeAt
返回一个字符串指定位置的码点(数字形式)
var s1 = '😀'
s1.charCodeAt(0) // 55357
String.prototyoe.at
返回一个字符串指定字符的码点(码点表示法)
var s1 = '😀'
s1.at() // '\uD83D'
s1.at(1) // '\uDE00'
'\uD83D' + '\uDE00' // '😀'
s1.at() + s1.at(1) // '😀'
如何判断是否是双字节字符
开始笨办法
先Array.from()把字符串转成数组。
然后判断第二个字符是不是双字节字符:
1 | var s = '12abcd中文😀0' |
阮一峰老师给了更好的办法。
如果是双字节字符,则第一个字节必须是0xD800
和0xDBFF
之间
1 |
|
原因后面可以补以下