从'𠮷'.length等于2去理解js中的字符串编码

877 阅读3分钟
  1. '𠮷'.length为什么是2
  2. Array.from('𠮷').length为什么是1

前置知识----计算机基础知识

众所周知,计算机底层的所有数据都是以010101的二进制形式存在

(一)现有的进制

  • 二进制: 0,1 表示 如:Number.parseInt('11', 2) === 3
  • 八进制: 以 8 为基数,第一个数字必须是零(0),然后是相应的八进制数字(数值 0~7)。如Number.parseInt('070', 8) === 56
  • 十进制:以10为基数,现实中几乎都是十进制,还记得学加法的逢10进1
  • 十六进制 : 数值前缀 0x/0X,然后是十六进制数字0~9以及A~F(不区分大小写)。Number.parseInt('0xA', 16) === Number.parseInt('0xa', 16) === 10

ps: Number.parseInt 解析字符串并返回指定基数的十进制整数

有了二进制,为什么要有8/10/16进制

更好的表示,你总不能写了一串的1010说这是10。二进制使用起来很不方便, 16/10/8进制可以解决这个问题。进制越大,数的表达长度也就越短。

如:十进制的256转化为二进制 parseInt(256).toString(2) === '100000000'

进制的转换原理

二、八、十、十六进制转换(图解篇)

比如二进制 → 十六进制(取四合一法) 11010111 ==> 7D

  1. 0111 = ( 0 * 2^3) + (12^2) + (12^1) + (1*2^0) = 7;

  2. 1101 = ( 1 * 2^3) + (12^2) + (02^1) + (1*2^0) = D(13);

(二)计算机中的存储单位(位bit)

  • 位 bit (比特)(Binary Digits):存放一位二进制数,即 0 或 1,最小的存储单位
  • 字节 byte:8个二进制位为一个字节(B),最常用的单位。 1B(bytes) = 8bit
  • 1KB (Kilobyte 千字节)=1024B
  • 1MB (Megabyte 兆字节 简称“兆”)=1024KB
  • 1GB (Gigabyte 吉字节 又称“千兆”)=1024MB
  • 1TB(Trillionbyte 万亿字节 太字节)=1024GB

(三) 常见字符编码

编码: 从一种格式转换为另一种格式,可以认为把0,1 翻译为你认识的字符,如英文,中文

ASCII 码

ASCII对照表

美国制定了一套字符编码,用单个字节对拉丁字母(英文字母)、阿拉伯数字(也就是 1234567890)、标点符号(,.!等)、特殊符号(@#$%^&等)以及一些具有控制功能(回车,换行)的字符与二进制位之间的关系,做了统一映射。这被称为 ASCII 码,一直沿用至今。共规定了128个字符的编码,比如空格SPACE是32(二进制00100000),大写的字母A是65(二进制01000001)

ps: 由于1个字节是8位,应该最多可以支持2^8 = 256种情况,其实ASCII 码高位始终为0.所以支持了2^7 = 128

标准的ASCII码就是基础的128位,而有些欧洲国家扩展了ASCII码的后128~255 被称为扩展的ASCII码

Unicode

汉字 Unicode 编码点范围

为了解决乱码问题,Unicode把所有语言都统一到一套编码里,它从 0 开始,为每个符号指定一个独一无二编号,这叫做”码点”(code point)。比如,码点 0 的符号就是 null(表示所有二进制位都是 0)。

注意: Unicode 只是一个符号集,而非编码,中文大多数是两个字节的码点,它只规定了符号跟二进制代码的映射关系,却没有规定这个二进制代码应该如何存储

unicode的问题:

假设 Unicode 统一规定每个符号用2个字节存储, 如果把ASCII编码的A用Unicode编码,只需要在前面补0就可以,因此,A的Unicode编码是00000000 01000001,多了一倍的存储空间,在存储和传输上就十分不划算

UTF-8

它是一种变长的编码方式(减少存储跟传输 ), 是目前在互联网上使用最广的一种 Unicode 的实现方式。把一个Unicode字符根据不同的数字大小编码成1-4个字节,常用的英文字母被编码成1个字节,常用汉字通常是3个字节,对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的

UTF-32

固定4个字节表示一个字符

UTF-16

UTF-16结合了定长和变长两种编码方法,编码长度要么是2个字节(U+0000到U+FFFF),要么是4个字节(U+010000到U+10FFFF,英文被编码为2个字节,常用汉字通常是4个字节

JS 中的字符串编码

  • JavaScript 字符串使用了两种 Unicode 编码混合的策略:UCS-2(使用2个字节表示已经有码点的字符)和 UTF-16。对于可以采用 16 位编码 (两个字节)的字符(U+0000~U+FFFF),这两种编码实际上是一样的
  • 两者的关系简单说,就是UTF-16取代了UCS-2,或者说UCS-2整合进了UTF-16。所以,现在只有UTF-16,没有UCS-2**(历史缘由)**

ES5 字符串中存在的问题

只能正确识别Unicode码点小于0xFFFF的字符,造成如下问题:

(1)length属性的值与肉眼所见不相符

(2)for循环中以16位编码为unit而非字符,substring slice repalce 也有同样的问题

let text = String.fromCodePoint(0x20BB7)//'𠮷' 
text.length  //2 => (1)字符串长度与肉眼所见不相符
for (let i = 0; i < text.length; i++) {
  console.log(text[i]); // 输出两个 [0]'\uD842'  [1]'\uDFB7'  => (2)循环错误
}
//(3)其他处理函数错误
text.substring(0,1) // '\uD842'
text.substring(0,2) // '𠮷'
// slice,repalce等的字符串方法的反应也是一样的 都只对2字节的码点有效

ES6对字符串的加强

原有的方法 String.prototype.charCodeAt(index) 返回0 到 65535(0xFFFF) 的UTF-16编码,如果是大于0xFFFF返回第一个编码单元

  • String.fromCodePoint(num1, /* …, */ numN):从Unicode码点返回对应字符, 可返回大于2字节的unicode码
  • String.prototype.codePointAt(index):从字符返回对应的码点
let text = String.fromCodePoint(0x20BB7)//'𠮷' 
text.codePointAt(0) //134071  
text.codePointAt(0).toString(16) // '20bb7' 大于2字节的unicode码点
Array.from(text).length //1 => (1)正确的长度
for (let str of text) {
  console.log(str); // 𠮷 => (2)循环正确
}
// (3) 将码点放入大括号,就能正确解读该字符
// 之前的双字节 
"\uD842\uDFB7" 
"\u20BB7"  // '₻7' 输出有误
// 现在
"\u{20BB7}"// "𠮷"

// (4) 正则增加了u修饰符,支持4字节码点  /^.$/ 只有一个字符
console.log(/^.$/.test(text)) //false
console.log(/^.$/u.test(text) )// true

// (5) 带附加符号的表示
// 方法一(单个字符)
'\u01D1'   // 'Ǒ'
// 方法二
'\u004F\u030C' // 'Ǒ'


// (6) 用 normalize方法修正
 '\u01D1'==='\u004F\u030C'  //false
'\u01D1'.normalize() === '\u004F\u030C'.normalize() 
 // true

扩展

js的字符字面量

  • \xnn 以十六进制编码 nn 表示的字符(其中 n 是十六进制数字 0~F),例如\x41 等于"A"
  • \unnnn 以十六进制编码 nnnn 表示的 Unicode 字符(其中 n 是十六进制数字 0~F),例如\u03a3 等于希腊字 符"Σ"
'一'.codePointAt(0)  // 获取unicode码点 19968
parseInt(19968).toString(16) //十六进制'4e00'
console.log('\u4e00') // 一  
console.log('\u002b;\x2b') // +;+

html字符的编码

html编码&#N;(十进制,N代表码点)或者&#xN;(十六进制,N代表码点)

<code>&#19968</code> // 显示一 十进制
<code>&#x4e00</code>  // 显示一 十六进制

css字符编码

\nnnn以十六进制编码 nnnn 表示的 Unicode 字符(其中 n 是十六进制数字 0~F)

.div::before{
    display: inline-block;
    content: "\4e00";
}

如有问题欢迎指正

参考