存储单位

数据存储是以“字节”(Byte)为单位,数据传输大多是以“位”(bit,又名“比特”)为单位,一个位就代表一个0或1(即二进制),每8个位(bit,简写为b)组成一个字节(Byte,简写为B),是最小数据存储单位。
1B = 8bit
1KB = 1024B = 8192bit
1MB = 1024KB = 1048576B = 8388608bit
1GB = 1024MB = 1048576KB = 1073741824B = 8589934592bit
1TB = 1024GB = 1048576MB = 1073741824KB = 1099511627776B = 8796093022208bit

字符编码

ASCII 码

我们知道,计算机内部,所有信息最终都是一个二进制值。每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从00000000到11111111。上个世纪60年代,美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。这被称为 ASCII 码,一直沿用至今。
ASCII 码一共规定了128个字符的编码,比如空格SPACE是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的一位统一规定为0。

Unicode

英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用 ASCII 码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。
可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是 Unicode,就像它的名字都表示的,这是一种所有符号的编码。

UTF-8

Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。

js 字符的Unicode表示法

js允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的Unicode码点(16进制)。
但是,这种表示法只限于码点在\u0000~\uFFFF之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。

  1. // a 10进制:[97] 16进制:[0x0061]
  2. // 𠮷
  3. // 32位 10进制:[134071] 16进制:[0x20BB7]
  4. // 16位 10进制:[55362 57271] 16进制:[0xD842 0xDFB7]
  5. // 汉字“𠮷”(注意,这个字不是“吉祥”的“吉”)的码点是0x20BB7(十进制为134071),
  6. // UTF-16 编码为0xD842 0xDFB7(十进制为55362 57271),需要4个字节储存。
  7. // 对于这种4个字节的字符,js不能正确处理,字符串长度会误判为2,
  8. // 而且charAt方法无法读取整个字符,charCodeAt方法只能分别返回前两个字节和后两个字节的值。
  9. console.log('\u0061'); //'a'
  10. console.log("\uD842\uDFB7"); //𠮷
  11. console.log("\u20BB7"); //₻7
  12. // 如果直接在\u后面跟上超过0xFFFF的数值(比如\u20BB7),JavaScript 会理解成\u20BB+7。
  13. // 由于\u20BB是一个不可打印字符,所以只会显示一个空格,后面跟着一个7。

ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。
  1. console.log('\u{20BB7}'); //'𠮷'
  2. console.log('\u{41}\u{42}\u{43}'); //'ABC'
  3. console.log('hell\u{6F}'); //'hello'
  4. // 大括号表示法与四字节的 UTF-16 编码是等价的
  5. console.log('\u{1F680}' === '\uD83D\uDE80'); //true
  6. console.log('z' === '\z'); //true
  7. console.log('z' === '\172'); //true
  8. console.log('z' === '\x7A'); //true
  9. console.log('z' === '\u007A'); //true
  10. console.log('z' === '\u{7A}'); //true
  11. console.log('z' === String.fromCharCode(0x007A) ); //true

ES5: charCodeAt、fromCharCode
  1. var str = "𠮷a";
  2. // 获取 16 位的 UTF-16 字符的码点
  3. console.log(str.charCodeAt(0), str.charCodeAt(1), str.charCodeAt(2)); //55362 57271 97
  4. // 获取 32 位的 UTF-16 字符的码点
  5. // 在第一个字符上,正确地识别了“𠮷”,返回了它的十进制码点 134071(即十六进制的20BB7)。
  6. // 在第二个字符(即“𠮷”的后两个字节)和第三个字符“a”上,codePointAt 方法的结果与 charCodeAt 方法相同。
  7. console.log(str.codePointAt(0), str.codePointAt(1), str.codePointAt(2)); //134071 57271 97

ES6: codePointAt、fromCodePoint
  1. // fromCharCode 不能识别 32 位的 UTF-16 字符(Unicode 码点大于 0xFFFF 的字符)
  2. console.log(String.fromCharCode(0x20BB7)); //'ஷ'
  3. // fromCodePoint 可以识别大于 0xFFFF 的字符
  4. console.log(String.fromCodePoint(0x20BB7)); //'𠮷'
  5. console.log(String.fromCodePoint(134071)); //'𠮷'
  6. console.log(String.fromCodePoint(55362, 57271)); //'𠮷'

Runes

Runes 对象是一个 32位 字符对象,用来表示一个字。这样设计也是考虑兼容 UTF-16 四个字节的情况。

  1. // a 10进制:[97] 16进制:[0x0061]
  2. // 𠮷
  3. // 32位 10进制:[134071] 16进制:[0x20BB7]
  4. // 16位 10进制:[55362 57271] 16进制:[0xD842 0xDFB7]

codeUnits、codeUnitAt 返回16位码点

  1. var s1 = '𠮷';
  2. print(s1.length); //2 表示占2个16位字符
  3. print('a'.codeUnits); //[97] 转化成16位[0x0061]
  4. print(s1.codeUnits); //[55362, 57271] 转化成16位[0xd842, oxdfb7] 转换成32位[134071]
  5. print(s1.codeUnitAt(0)); //55362
  6. print(s1.codeUnitAt(1)); //57271

runes 返回32位 码点的 Runes

  1. var s1 = '𠮷';
  2. print(s1.runes); //(134071)
  3. print(s1.runes.runtimeType); //Runes
  4. print(s1.runes.length); //1 表示占1个32位字符

fromCharCode、fromCharCodes 码点、Runes转为字符

  1. var s1 = '𠮷';
  2. var s2 = s1.runes;
  3. print(String.fromCharCodes(s2)); //𠮷
  4. print(String.fromCharCode(134071)); //𠮷
  5. print('\ud842\udfb7'); //𠮷
  6. print('\u{20BB7}'); //𠮷
  7. print('\u0061'); //a