后端(nodejs)是Buffer
来存放二进制数据,这个对象前端(浏览器)并没有,但前端也有一些场景会设置到二进制数据,例如文件上传,那前端的又是如何存放二进制数据的?
二进制数
-
什么是二进制?
二进制 (binary)在数学和数位电路中指以2为底数的记数系统,以2为基数代表系统是二进位制的。这一系统中,通常用两个不同的数字0和1来表示。数字电子电路中,逻辑门直接采用了二进制,因此现代的计算机和依赖计算机的设备里都用到二进制。每个数字称为一个位元(二进制位)或比特(Bit,Binary digit 的缩写)
-
JS中如何表示一个二进制数?
js// 0b或0B开头表示二进制数 const num1 = 0b11; // 其他进制: // 0o或0O开头表示八进制数 const num2 = 0o11; // 0x或0X开头表示十六进制数 const num3 = 0x11; // 输出:3 9 17 console.log(num1, num2, num3);
-
如何得到字符的二进制数?
- 字符串的
charCodeAt
方法可返回指定位置的字符的 Unicode 编码,返回值是 0 - 65535 之间的整数,表示给定索引处的 UTF-16 代码单元。 - 数字的
toString([radix])
方法可以返回数字的指定进制的字符串形式
jsfunction printCharcode(char) { const charcode = char.charCodeAt(0); const bitNum = charcode.toString(2); console.log(`字符[${char}]的unicode编码为${charcode},二进制为${bitNum}`); } printCharcode('A'); printCharcode('𠮷'); // 输出: // 字符[A]的unicode编码为65,二进制为1000001 // 字符[𠮷]的unicode编码为55362,二进制为1101100001000010
- 字符串的
二进制对象
如果以数字类型来操作二进制数据,显然不够,实际处理二进制数据,需要使用相关对象
ArrayBuffer
-
ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区,是一个字节数组
-
创建
ArrayBuffer
js// 创建一个n个字节的缓冲区 const buffer = new ArrayBuffer(2); // byteLength为字节长度,内容会被初始化为0 console.log(buffer); // nodejs执行后输出:ArrayBuffer { [Uint8Contents]: <00 00>, byteLength: 2 }
-
不能直接操作
ArrayBuffer
中的内容,而是要通过TypedArray
或DataView
对象来操作。它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容
TypedArray
- 一个 TypedArray对象描述了底层二进制数据缓冲区的类数组视图
- 没有称为
TypedArray
的全局属性,也没有直接可用的TypedArray
构造函数,但是有很多不同的全局属性,其值是指定元素类型的类型化数组构造函数(子类)。TypedArray
作为一个"抽象类",为所有类型化数组的子类提供了实用方法的通用接口。
子类
完整见MDN
-
字节大小
这里的字节大小指的是单个元素的字节大小,有静态方法可以获取:jsconsole.log(Int8Array.BYTES_PER_ELEMENT, Int16Array.BYTES_PER_ELEMENT, Int32Array.BYTES_PER_ELEMENT); // 输出为:1 2 4
-
元素溢出
- 有符号
在【描述】列中,有符号的对象中有【补码】二字,是指如果数组中的元素超过了值范围应该如何处理
jsconsole.log(new Int8Array([128])); // Int8Array(1) [ -128 ] /** * 有符号数第一位表示符号,0为正数,1为负数 * 128的二进制数是:010000000 * 长度溢出,截取8位:10000000 * 执行取反:11111111 * 然后加一:100000000,对应-128 */ console.log(new Int8Array([130])); // Int8Array(1) [ -126 ] /** * 130的二进制数是:10000010,int8中表示负数 * 执行取反:11111101 * 然后加一:11111110,int8中对应-126 */
- 无符号
上边界溢出,取有效位;下边界溢出,转为等价无符号数
jsconsole.log(new Uint8Array([300])); // Uint8Array(1) [ 44 ] // 300的二进制数是:100101100 // 长度溢出,截取8位:00101100 // 对应十进制数:44 console.log(new Uint8Array([-1])); // Uint8Array(1) [ 255 ]
- 有符号
API
有基本的数组的方法,比如join
,也有一些特有的属性,比如byteLength
js
const typedArray1 = new Int8Array(4);
typedArray1[0] = 32;
typedArray1[1] = 127;
typedArray1[2] = 130;
typedArray1[3] = -100;
console.log(typedArray1); // Int8Array(4) [ 32, 127, -126, -100 ]
console.log(typedArray1.byteLength, typedArray1.length); // 4 4
// 普通数组的方法TypedArray都有
console.log(typedArray1.includes(32)); // true
console.log(typedArray1.join(';')); // 32;127;-126;-100
const typedArray2 = new Int16Array([32, 127, 128]);
console.log(typedArray2); // Int16Array(3) [ 32, 127, 128 ]
// 数组的长度(length) * 每个元素的字节数(BYTES_PER_ELEMENT) = 数组字节数(byteLength)
console.log(typedArray2.byteLength, typedArray2.length); // 6 3
API中也体现了TypedArray
和ArrayBuffer
的关系
-
ArrayBuffer
实例化TypedArray
jsconst buffer = new ArrayBuffer(2); console.log(buffer);// ArrayBuffer { [Uint8Contents]: <00 00>, byteLength: 2 } const typedArray3 = new Int8Array(buffer);
-
读取
ArrayBuffer
jsconsole.log(typedArray3.buffer);// ArrayBuffer { [Uint8Contents]: <00 00>, byteLength: 2 }
-
修改
ArrayBuffer
jstypedArray3[0] = 100; console.log(typedArray3.buffer); // ArrayBuffer { [Uint8Contents]: <64 00>, byteLength: 2 } console.log(buffer); // ArrayBuffer { [Uint8Contents]: <64 00>, byteLength: 2 }
DataView
DataView 视图是一个可以从二进制 ArrayBuffer
对象中读写多种数值类型的底层接口,使用它时,不用考虑不同平台的字节序问题。
-
实例化
DateView
jsconst buffer = new ArrayBuffer(2); console.log(buffer); // ArrayBuffer { [Uint8Contents]: <00 00>, byteLength: 2 } const view = new DataView(buffer); /* DataView { byteLength: 2, byteOffset: 0, buffer: ArrayBuffer { [Uint8Contents]: <00 00>, byteLength: 2 } } */ console.log(view);
-
修改
ArrayBuffer
js// 在视图开始的指定字节偏移处存储一个带符号 8 位整数 view.setInt8(0, 120); view.setInt8(1, 110); /* DataView { byteLength: 2, byteOffset: 0, buffer: ArrayBuffer { [Uint8Contents]: <78 6e>, byteLength: 2 } } */ console.log(view); console.log(buffer); // ArrayBuffer { [Uint8Contents]: <78 6e>, byteLength: 2 } // 120 110 30830 console.log(view.getInt8(0), view.getInt8(1), view.getInt16(0));
DateView
的API还有一些类似的,比如getInt32
,setInt32
,以上只展示了部分- 为什么
view.getInt16(0)
的值是30830?
buffer
设置值后第一个字节为01111000(120),第二个字节为01101110(110),getInt16
表示从视图开始的指定字节偏移处获取一个带符号 16 位整数,返回值为0111100001101110,对应十进制30830
Blob
Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream
来用于数据操作。
API
-
创建
Blob
对象jsconst obj = {hello: 'world'}; const blob = new Blob([JSON.stringify(obj, null, 2)], {type : 'application/json'});
-
读取
Blob
对象的ArrayBuffer
-
使用
arrayBuffer
方法jsblob.arrayBuffer().then(buffer => { console.log(buffer); });
-
使用
FileReader
jsconst reader = new FileReader(); reader.addEventListener('loadend', () => { console.log(reader.result); }); reader.readAsArrayBuffer(blob);
-
- 读取
Blob
对象所有内容的UTF-8 格式的字符串-
使用
text
方法jsblob.text().then(text => { console.log(text); });
-
使用
FileReader
jsconst reader2 = new FileReader(); reader2.readAsText(blob); reader2.addEventListener('loadend', () => { console.log(reader2.result); });
-
-
将
Blob
对象转为base64
使用FileReader
jsconst reader3 = new FileReader(); reader3.readAsDataURL(blob); reader3.addEventListener('loadend', () => { console.log('readAsDataURL: ', reader3.result); });
-
生成
Blob
对象的内存URL
使用URL.createObjectURL
,生成的url可以预览、下载文件html<!DOCTYPE html> <html> <body> <h3>Blob对象</h3> </body> <script> const obj = {hello: 'world'}; const blob = new Blob([JSON.stringify(obj, null, 2)], {type : 'application/json'}); const url = URL.createObjectURL(blob,{type: 'text/plain'}); const previewLink = document.createElement('a'); previewLink.href = url; previewLink.innerText = '预览文件'; const downloadLink = document.createElement('a'); downloadLink.href = url; downloadLink.innerText = '下载文件'; downloadLink.download = 'test.json'; document.body.appendChild(previewLink); document.body.appendChild(document.createElement('br')); document.body.appendChild(downloadLink); </script> </html>
File
File 接口基于 Blob
,继承了 blob 的功能并将其扩展以支持用户系统上的文件。
html
<!DOCTYPE html>
<html>
<body>
<h3>File对象</h3>
<input type="file" id="fileInput"></input>
<br/>
</body>
<script>
const fileInput = document.getElementById('fileInput');
fileInput.addEventListener('change', function() {
const file = fileInput.files[0];
console.log(file);
file.arrayBuffer().then(buffer => {
console.log(buffer);
});
});
</script>
</html>
使用场景
了解了各个对象以及他们的关联后,除了File
对象用在文件上传,其他的对象有什么实际场景吗?
下载Canvas绘制的图片
Canvas对象 -> DataURL对象 -> TypedArray对象 -> Blob对象 -> createObjectURL(下载的链接)
html
<!DOCTYPE html>
<html>
<body>
<h3>下载Cavas绘制的图片</h3>
<canvas id="myCanvas" width="200" height="200"
style="border:1px solid #000000;">
<br/>
</body>
<script>
function dataURItoBlob(dataUrl) {
console.log('dataUrl: ', dataUrl);
const arr = dataUrl.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
console.log('mime: ', mime);
// atob() 方法用于解码使用 base-64 编码的字符串。
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
// returns an integer between 0 and 65535 representing the UTF-16 code unit at the given index.
// 因为是base64编码,字符实际范围在[0,255]之间,用Uint8Array就行,不用Uint16Array
u8arr[n] = bstr.charCodeAt(n);
}
console.log('u8arr: ', u8arr);
return new Blob([u8arr], {type: mime});
}
</script>
<script>
// 1. 绘制canvas
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var grd=ctx.createLinearGradient(0,0,200,0);
grd.addColorStop(0,"red");
grd.addColorStop(1,"white");
ctx.fillStyle=grd;
ctx.fillRect(0,0,200,200);
ctx.font="30px Arial";
ctx.strokeText("Hello World",10,50);
// 2. 将cavas转为blob
var dataUrl = canvas.toDataURL("image/jpeg");
var blob = dataURItoBlob(dataUrl);
console.log(blob);
// 3. 下载blob对应的文件
var url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'test.jpeg';
link.innerText = '下载文件';
document.body.insertBefore(link, canvas);
</script>
</html>