位运算符是一切二进制数据操作的基础,在js
里总共只有7
个符号,看似容易但要熟练起来需要一定的练习。
W3school 这篇文章讲得很清晰: ECMAScript 位运算符
简单总结下,所有数据都可以用字节表示,一个字节就是1Byte
,1024Byte
就是1KB
这个大家都懂的,但它需要转换成 0101...
这种机器能读得多的符号为二进制代码,其中1Byte
可以转换成8
个这个二进制代码,例如数字1
可以转化为 00000001
,数字3
可以转化为00000010
这个也很好理解。
那么,1Byte
最大能表示多大的数字呢?那就是二进制代码的11111111
啦,转换成10
进制就是255
。
parseInt("11111111", 2) === 255;
但有时候觉得用11111111
去表示一个数字,对机器来说是没问题的,但对人的阅读来说可能不是很方便,所以就有了除10
进制以外的进制表示方法,例如常用的16
进制,那么11111111
可以转换成16
进制的FF
;
(255).toString(16) === "ff";
通常你说的ff
鬼知道它是16
进制还是32
进制啊,所有我们约定在数字前面加上进制表示的符号,例如:
// 16进制前面带有0x, 2进制前面带有0b
0xff === 0b11111111;
题外话,常见的颜色值可以用6
位的16
进制表示,例如白色为#ffffff
,黑色为#000000
,请问这个区间可以表示多少种颜色值?
parseInt("ffffff", 16) === 16777215;
大致理解后,再阅读 W3school 的文章就容易很多了,那么就来练习一个简单的场景:
获得有一个字节的流为00011010
,其中第4
位到第7
位(1101
)表示了一个无符号整数,我怎么知道这个数是多少?
((0b00011010 >> 1) & 0b00001111) === 13;
// 或者
((0b00011010 >> 1) & 15) === 13;
又或者,现在有两个字节00011010
和11000000
,第一个字节的低位 4 个比特(1010
)和第二个字节的高位 2 个比特(11
)表示了一个无符号整数,我怎么知道这个数是多少?
(((0b00011010 & 0b00001111) << 2) | (0b11000000 >> 6)) === 43;
// 或者
(((0b00011010 & 15) << 2) | (0b11000000 >> 6)) === 43;
可以看出位运算是内部以二进制进行运算,但都是以10
进制输出的,而且给人的感觉就是&
用于获取数据,而|
则用于写入数据。
字节序是初学者不容易理解的概念,甚至在操作时会产生疑惑,但实际上并不用太关心,因为一般一种数据就只有一种字节序。
可以看看阮一峰的理解字节序
简单总结下,就是单次写入多个字节的时候,这些字节是怎么排列的,有时候是从大到小是我们直观的表示方式,有时候却是从小到大排。
例如我有两个字节分别是数字20
和19
,假如把它看成一个年份,我们最直观的阅读方式就是2019
啦,这就是大端字节序。但假如要用小端字节序的话就是反过来1920
,所以字节序没弄清的话会造成很大的差别。
那么怎么判断当前环境是哪个字节序呢?这其实很简单,但关于相关语法后面再讲解。
// 新建一个多字节的缓冲,用 Uint16Array 和 Uint32Array 都可以
var a = new Uint32Array([0x12345678]);
// 再以Uint8Array查看排列顺序
// 假如是大端字节序就会是:[12, 34, 56, 78]
// 假如是小端字节序就会是:[78, 56, 34, 12]
var b = new Uint8Array(a.buffer);
var BigEndian = b[0] == 0x12;
关于 ASCII,Unicode 和 UTF-8 可以网上查一下,了解一下
实际场景中,二进制数据不单单只返回数字,其中还可以返回字符串,这里简单说下数字和Unicode
字符串的互转,以后肯定会用到。
// 数字转Unicode
String.fromCharCode.call(String, 20013) === "中";
// Unicode转数字
String.prototype.charCodeAt.call("中", 0) === 20013;
// 或者
"中".charCodeAt(0) === 20013;
Base64
在数据保存和传输也经常用到,有了以上基础,再看Base64
原理时就容易得多了。
参考了阮一峰的Base64 笔记
所谓 Base64,就是说选出 64 个字符----小写字母 a-z、大写字母 A-Z、数字 0-9、符号"+"、"/"(再加上作为垫字的"=",实际上是 65 个字符)----作为一个基本字符集。然后,其他所有符号都转换成这个字符集中的字符。
- 将每三个字节作为一组,一共是
24
个二进制位。 - 将这
24
个二进制位分为四组,每个组有6
个二进制位。 - 在每组前面加两个
00
,扩展成32
个二进制位,即四个字节。 - 根据下表,得到扩展后的每个字节的对应符号,这就是
Base64
的编码值。
因为,Base64
将三个字节转化成四个字节,因此 Base64
编码后的文本,会比原文本大出三分之一左右。
要注意一点,常见的Base64
字符串前面都有数据类型和编码方式,例如data:image/png;base64,
,这部分是给服务器或者浏览器识别的,并不是Base64
的一部分,编码时要区分出来。
浏览器自带的Base64
编码和反编码方法:
btoa("ABC") === "QUJD";
atob("QUJD") === "ABC";
Nodejs 自带的Base64
编码和反编码方法:
Buffer.from("ABC").toString("base64") === "QUJD";
Buffer.from("QUJD", "base64").toString("ascii") === "ABC";