走进前端二进制

69 阅读10分钟

二进制对象

总共有五个大对象,BlobArrayBufferFileFileReaderTypedArray

  • BlobArrayBufferFile 可以归为一类,它们都是数据;
  • FileReaderTypedArray 算是工具,用来读取对应数据;

Blob

Blob 全称是 binary large objectBlob 对象就是一个包含有只读原始数据的类文件对象,通俗讲就是不可修改的二进制文件。在数据库管理系统中,将二进制数据存储为一个单一个体的集合。Blob 通常是影像、声音或多媒体文件。

创建 Blob 对象

// 第一个参数可以是 ArrayBufferView / Blob / ArrayBuffer / DOMString 等对象构成的数组,其中 DOMString 会被编译为 utf-8
let blobParts = ['<html><h2>Hello Fall</h2></html>']
let options = {type : 'text/html'}
// options:一个可选对象,包含两个属性
// type:默认为 '' 表明放到 blob 中的数组内容的 MIME 类型(见注释)
// endings:用于指定包含行结束符的 `n` 字符串如何被写入,默认为 "transparent" 保持 blob 中的格式不变,可选为 "native",代表行结束符,会被更改为适合宿主操作系统文件系统的换行符
let myBlob = new Blob(blobParts,options)
// 此时 myBlob 是一个对象,拥有 size, type 两个属性
console.log(myBlob.size + " bytes"); // myBlob.size 用字节表示的数据大小(只读)
// 37 bytes
console.log("MIME type is " + myBlob.type); // myBlob.type 表示 MIME 类型(只读)
// MIME 类型为 text/html

MIME:Multipurpose Internet Mail Extensions:多用途互联网邮件扩展类型,用来区分文件的种类。

Blob 上面的方法

// Blob 对象是不可改变的,但是可以通过 slice 进行分割
// start:分割起始点 end:分割终点 contentType:新的MIME类型
Blob.prototype.slice(start, end ,contentType)
// 返回一个能读取 Blob 内容的 ReadableStream
Blob.prototype.stream()
// 返回一个 Promise 对象,且包含 blob 所有内容的
Blob.prototyoe.text()
// 转换为 arrayBuffer 异步转换返回一个 Promise 对象
blob.arrayBuffer().then((res) => {
  console.log(res);
});

Blob URL

将一个 file 或者 Blob 类型的数据转换为 UTF-16 的字符串,并保存在当前操作的 document 下,并存储在内存中。

类似这样一个链接
blob:http://localhost:3000/53acc2b6-f47b-450f-a390-bf0665e04e59
// 生成 blob url
const blob = new Blob(['ddididid'],{type:'text/plain'})
const file = new File(['fall'],{type:'text/plain'})
const fileURL = URL.createObjectURL(file) // 也可以放入 blob
// 生成的 URL 存储了一个 URL → Blob 映射。
// 数据存储后会常驻内存,只有页面 unload() 事件触发,或者使用手动清除内存占用
URL.revokeObjectURL(fileURL) // 手动清除 document 中的 blob
// URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的 URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的 URL 对象表示指定的 File 对象或 Blob 对象。

前端下载经常会使用这些内容

const downloadFile = async (params, fileName) => {
  // 我们使用axios设置接口返回类型 responseType: "blob", 所以这里从后端返回的是blob。
  const results = await download(params);
  const a = document.createElement("a");
  a.download = fileName + ".xlsx";
  // 生成blob url。这里可以使用Blob对象或者File对象
  a.href = URL.createObjectURL(results);
  a.style.display = "none";
  document.body.appendChild(a);
  a.click();
  // 释放内存
  window.URL.revokeObjectURL(a.href);
  document.body.removeChild(a);
};

Base64

Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法,它常用于在处理文本数据的场合,表示、传输、存储一些二进制数据,包括 MIME 的电子邮件及 XML 的一些复杂数据。

在编写 HTML 网页时,对于一些较小的图片,通常会选择将图片内容直接内嵌在网页中,从而减少不必要的网络请求。浏览器支持 Data URLs 的特性,可以允许使用 base64 对图片或其他文件的二进制数据进行编码,将其作为文本字符串嵌入网页中。

Data URLs 的数据由四部分组成
前缀:[data:] 指示数据类型的 MIME 类型[<mediatype>]
如果非文本则为可选的 base64 标记:[;base64]、数据本身:,<data>
data:[<mediatype>][;base64],<data>
示例:
data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...

js 内置了两个方法进行字符串的 Base64 编码和解码

const str = 'Fall'
// 编码
const b = window.btoa(str)
b // "RmFsbA==" 
// 解码
const str2 = window.atob(b)
str2 // "Fall" 

用途和特性:

  • 将较小的图片转为字符串,拼接后进行传输,减少请求次数。
  • 对数据进行加密(不安全)
  • 编码后体积变大,至少增加 33%,如果过是一个字节,至少变为三个字节
  • 编码也需要消耗额外工作量

File

File 对象继承自 Blob,也是二进制对象,通常用在 <input type='file'> 选择的 FileList 对象。或者是拖拽产生的 DataTransfer 对象。

var oneFile = new File(array,name,options)
// array 数据构成的数组
// name 文件名
// options 设置一些属性,Type 属性,lastModified
// File 对象上的属性
oneFile.name// 文件名
oneFile.size// 文件大小
oneFile.lastModified // 最后修改时间的时间戳
oneFile.lastModifiedDate // 最后修改时间的 Date 对象
oneFile.type // MIME类型

方法继承自 Blob

File.prototype.slice(start, end ,contentType)
File.prototype.stream()
File.prototyoe.text()
File.arrayBuffer().then((res) => {
  console.log(res);
});

二进制操作

对象关系

BlobArrayBuffer 的关系

  • ArrayBuffer 是底层的二进制数据,可以借助工具进行读写。
  • Blob 对象是不可变的,可以接受一个 ArrayBuffer 作为参数生成的一个 Blob 对象,相当于将 ArrayBuffer 封装为一个整体。拿到的是一个整体,你能看到大小,类型,但是不能看到细节。
  • BlobArrayBuffer 对象之间是可以相互转化的:

ArrayBufferTypedArray 的关系

  • ArrayBuffer 表示纯 01 构成的串,不知道第一个元素和第二个元素需要如何分割
  • TypedArray 九中类型的构造函数,根据九种类型,去分割 ArrayBuffer 的纯 01 串

FileReader

FileReader 可以异步读取存储在用户计算机上(或者原始数据缓冲区)的 BlobFile 文件,读取为不同的格式。

作为工具,可以将 Blob、File 转换为 ArrayBuffer

使用

// 创建一个 fileReader 对象
const fileReader = new FileReader()
// 属性
fileReader.error // 表示在读取过程中出现错误
fileReader.result // 返回文件的内容,读取完成后,该属性才会生效,result 会根据不同的读取方式有不同的结果
fileReader.readyState // 表示当前文件的读取状态,0:没有加载任何数据,1:数据正在被加载,2:已经完成全部读取请求

方法

// 无论读取成功与否,并不会返回读取的结果,结果会被存储在 fileReader.result 属性中
// 所以一般读取成功后通过 onload 事件,输出 fileReader.result
fileReader.abort() // 终止读取操作,readyState 为 2
fileReader.readAsArrayBuffer() // 将读取的内容转换为 ArrayBuffer
fileReader.readAsBinaryString()
fileReader.readAsDataURL() // 将数据转化为 Base64 的 data url
fileReader.readAsText() // 将二进制数据读取并编码为字符串形式

事件

fileReader.onabort // 读取操作中断时触发
fileReader.onerror // 当出现错误时触发
fileReader.onload // 读取操作完成时触发
fileReader.onloadstart // 读取操作开始时触发
fileReader.onloadend // 读取操作结束时触发,成功或者失败
fileReader.onprogress // 处理 progrress 事件,读取 Blob 时触发

使用

// 读取二进制文件
const blob = new Blob(['hello','everyday'],{type:'text/plain'})
const fileReaderOne = new FileReader()
fileReader.readAsDataURL(blob)
fileReader.onload=()=>{
  console.log(fileReader) // 成功后 fileReader.result 是想要的结果
}
<!-- 读取文件 -->
<input type="file" accept="image/*" onchange="loadFile(event)">
<img id="output"/>
<script>
  const loadFile = function(event) {
    const reader = new FileReader();
    // readAsDataURL 会把文件读取为 base64 的 dataURL
    reader.readAsDataURL(event.target.files[0]); // 或者通过 node.files 获取文件
    reader.onload = function(){
      const output = document.querySelector('#output')
      output.src = reader.result; // reader.result 即读取后的内容
    };
  };
</script>

ArrayBuffer

ArrayBuffer 对象用于表示通用的,固定长度的原始二进制数据。

它是内存上的一段二进制数据,你不能直接操纵 ArrayBuffer 的内容,而是需要创建一个类型化数组对象(TypeArray)或 DataView 对象进行操作,该对象以特定格式表示缓冲区,并使用该对象读取和写入缓冲区的内容。

Blob 作为一个整体的文件,适合用于传输。只有关注文件的细节修改一部分的数据,才会用到 ArrayBuffer

// Blob 和 ArrayBuffer 之间相互转化
​
​

TypedArray

一组构造函数,一共包含九种类型,每一种都是一个构造函数。

TypedArray 的构造函数接受三个参数,第一个 ArrayBuffer(其实还可以是数组、视图这里不细说)对象,第二个视图开始的字节号(默认0),第三个视图结束的字节号(默认直到本段内存区域结束)。

名称占用字节描述
Int8Array18位有符号整数
Uint8Array18位无符号整数
Uint8ClampedArray18位无符号整型固定数组(数值在0~255之间)
Int16Array216位有符号整数
Uint16Array216位无符号整数
Int32Array432 位有符号整数
Uint32Array432 位无符号整数
Float32Array432 位 IEEE 浮点数
Float64Array864 位 IEEE 浮点数
const buffer =  [ 126, 226 ] // 可以是数组、视图、ArrayBuffer
const uint8 = new Uint8Array(buffer); // 将 ArrayBuffer 中的每个字节视为 0 到 255 之间的单个数字(每个字节是 8 位)。这称为 “8 位无符号整数”。
const uint16 = new Uint16Array(buffer); // 将每 2 个字节视为一个 0 到 65535 之间的整数。这称为 “16 位无符号整数”。
const uint32 = new Uint32Array(buffer);// 将每 4 个字节视为一个 0 到 4294967295 之间的整数。这称为 “32 位无符号整数”。
const float64 = new Float64Array(buffer);//  将每 8 个字节视为一个 5.0x10-324 到 1.8x10308 之间的浮点数。

DataView

更灵活的视图,DataView 视图支持除 Uint8ClampedArray 以外的八种类型。

const dataView1 = new DataView(buffer);
console.log(dataView1);
console.log(dataView1.getUint8(0));
console.log(dataView1.getUint16(0));
console.log(dataView1.getUint32(0));
console.log(dataView1.getFloat64(0));

互相转换

image-20230102200409713

Blob 和 File

// File 转换为 Blob
let file = new File(['文件'],'fileName.txt',{type:'text/plain'})
let blob = new Blob([file],{type:file.type})
​
// Blob 转化为 File
let blob = new Blob(['blob文件',{type:'text/plain'}])
let oneFile = new File([Blob],'fileName',{type:blob.type})

转为 base64

// File / Blob 转为 base64
function toDataURL(blob,callBack){
  let a = new FileReader()
  a.onload = function (e){callBack(e.target.result)}
  a.readAsDataURL(blob)
}
const blob = new Blob(['hello','fall'],{type:'text/plain'})
const file = new File(['byte','me'],'allright')
toDataURL(blob,(result)=>{
  console.log(result)
})
toDataURL(file,(result)=>{
  console.log(result)
})

img 转为 Base64

const imgToBase64 = (imgURL)=>{
  let image = new Image()
  image.src = imgURL
  return new Promise(resolve=>{
    image.onload=()=>{
      let canvas = document.createElement('canvas')
      canvas.width = image.width
      canvas.height = image.height
      let context = canvas.getContext('2d')
      context.drawImage(image,0,0,image.width,image.height)
      let dataURL = canvas.toDataURL('image/png')
      resolve(dataURL)
    }
  })
}
imgToBase64("../vue2/src/assets/logo.png").then((res) => {
  console.log(res);
});

Base64 转换

转为 Blob

function dataURLtoBlob(dataurl) {
  // `data:[<mediatype>][;base64],<data>`
  var arr = dataurl.split(","),
    mime = arr[0].match(/:(.*?);/)[1],
    bstr = atob(arr[1]),
    n = bstr.length,
    u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new Blob([u8arr], { type: mime });
}
​

转为 File

function dataURLtoFile(dataurl, filename) {
  //将base64转换为文件
  var arr = dataurl.split(","),
    mime = arr[0].match(/:(.*?);/)[1],
    bstr = atob(arr[1]),
    n = bstr.length,
    u8arr = new Uint8Array(n);
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n);
  }
  return new File([u8arr], filename, { type: mime });
}

Blob 和 ArrayBuffer

将二进制内容转换为可以读写的 ArrayBuffer

const name = 'fall'
const blob = new Blob(name,{type:'text/plain'})
​
function readBlob (blob, type) {
  return new Promise(resolve => {
    const reader = new FileReader()
    reader.readAsArrayBuffer(blob)
    reader.onload = function (e) {
      resolve(e.target.result)
    }
  })
}
​
readBlob(blob).then(res=>{
   new Unit8Array(res)
})

使用场景

文件下载

const download = (fileName, blob) => {
  const link = document.createElement("a");
  link.href = URL.createObjectURL(blob);
  link.download = fileName;
  link.click();
  link.remove();
  URL.revokeObjectURL(link.href);
};
const downloadBtn = document.querySelector("#downloadBtn");
downloadBtn.addEventListener("click", (event) => {
  const fileName = "blob.txt";
  const myBlob = new Blob(["一文彻底掌握 Blob Web API"], { type: "text/plain" });
  download(fileName, myBlob);
});

图片压缩

在前端要实现图片压缩,我们可以利用 Canvas 对象提供的 toDataURL() 方法,该方法接收 typeencoderOptions 两个可选参数。

// compress.js
const MAX_WIDTH = 800; // 图片最大宽度
function compress(base64, quality, mimeType) {
  let canvas = document.createElement("canvas");
  let img = document.createElement("img");
  img.crossOrigin = "anonymous";
  return new Promise((resolve, reject) => {
    img.src = base64;
    img.onload = () => {
      let targetWidth, targetHeight;
      if (img.width > MAX_WIDTH) {
        targetWidth = MAX_WIDTH;
        targetHeight = (img.height * MAX_WIDTH) / img.width;
      } else {
        targetWidth = img.width;
        targetHeight = img.height;
      }
      canvas.width = targetWidth;
      canvas.height = targetHeight;
      let ctx = canvas.getContext("2d");
      ctx.clearRect(0, 0, targetWidth, targetHeight); // 清除画布
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      let imageData = canvas.toDataURL(mimeType, quality / 100);
      resolve(imageData);
    };
  });
}

生成 PDF

import jspdf from 'jspdf'
function generatePdf() {
  const js2pdf = new jsPDF();
  js2pdf.text("Hello semlinker!"6688);
  const blob = new Blob([js2pdf.output()], { type"application/pdf" });
  blob.text().then((blobAsText) => {
    console.log(blobAsText);
  });
}

图片预览

FileReader API,我们也可以方便的实现图片本地预览功能,具体代码如下:

<input type="file" accept="image/*" onchange="loadFile(event)">
<img id="output"/>
<script>
  const loadFile = function(event) {
    const reader = new FileReader();
    // readAsDataURL 会把文件读取为 base64 的 dataURL
    reader.readAsDataURL(event.target.files[0]);
    reader.onload = function(){
      const output = document.querySelector('#output')
      output.src = reader.result;
    };
  };
</script>

强制检查文件类型

对于不同类型的文件,初始的几个字节内容都是固定的。

文件都是三十二位,二进制存储。比如说,D4 就是 1 Byte,也就是 8 Bit。

文件类型文件后缀魔数
JPEGjpg/jpeg0xFF D8 FF
PNGpng0x89 50 4E 47 0D 0A 1A 0A
GIFgif0x47 49 46 38(GIF8)
BMPbmp0x42 4D
PDFpdf0x25 50 44 46 对应的字符串为(%PDF)

问题来了,一个文件,开头是 89 50 4E 47,那么它是什么类型文件?查表可知:png

function readBuffer(file,start=0,end=2){
  return new Promise((resolve,reject)=>{
    const reader = new FileReader()
    reader.onerror = rejcet
    reader.onload=()=>{
      resolve(reader.result)
    }
    reader.readAsArrayBuffer(file.slice(start,end))
  })
}
function check(headers){
  return (buffers,options={offset:0})=>headers.every((header,index)=>header===buffers[options.offset+index])
}
// 高阶函数,生成对应的函数
// 翻译验证对应的函数
// 比如说,生成检查 PNG 的函数
const isPNG = check([0x98,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a])

有一个检查类型的库:file-type

参考文章

作者(文章名称)连接
controZLjuejin.cn/post/691579…
阿宝哥juejin.cn/post/698014…
你不知道的 Blobcaibaojian.com/blob.html
苏苏同学juejin.cn/post/704631…
semlinkermp.weixin.qq.com/s?__biz=MzI…
山月行caibaojian.com/binary.html
northUCjuejin.cn/post/699098…