前端怎么处理二进制数据?

后端(nodejs)是Buffer来存放二进制数据,这个对象前端(浏览器)并没有,但前端也有一些场景会设置到二进制数据,例如文件上传,那前端的又是如何存放二进制数据的?

二进制数

  1. 什么是二进制?

    二进制binary)在数学和数位电路中指以2为底数的记数系统,以2为基数代表系统是二进位制的。这一系统中,通常用两个不同的数字0和1来表示。数字电子电路中,逻辑门直接采用了二进制,因此现代的计算机和依赖计算机的设备里都用到二进制。每个数字称为一个位元(二进制位)或比特(Bit,Binary digit 的缩写)

  2. 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);
  3. 如何得到字符的二进制数?

    • 字符串的charCodeAt 方法可返回指定位置的字符的 Unicode 编码,返回值是 0 - 65535 之间的整数,表示给定索引处的 UTF-16 代码单元。
    • 数字的toString([radix]) 方法可以返回数字的指定进制的字符串形式
    js 复制代码
    function 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

  1. ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区,是一个字节数组

  2. 创建ArrayBuffer

    js 复制代码
    // 创建一个n个字节的缓冲区
    const buffer = new ArrayBuffer(2);
    // byteLength为字节长度,内容会被初始化为0
    console.log(buffer); 
    
    // nodejs执行后输出:ArrayBuffer { [Uint8Contents]: <00 00>, byteLength: 2 }
  3. 不能直接操作 ArrayBuffer 中的内容,而是要通过TypedArrayDataView 对象来操作。它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容

TypedArray

  1. 一个 TypedArray对象描述了底层二进制数据缓冲区的类数组视图
  2. 没有称为 TypedArray 的全局属性,也没有直接可用的 TypedArray 构造函数,但是有很多不同的全局属性,其值是指定元素类型的类型化数组构造函数(子类)。TypedArray 作为一个"抽象类",为所有类型化数组的子类提供了实用方法的通用接口。

子类

完整见MDN

  1. 字节大小
    这里的字节大小指的是单个元素的字节大小,有静态方法可以获取:

    js 复制代码
    console.log(Int8Array.BYTES_PER_ELEMENT, Int16Array.BYTES_PER_ELEMENT, Int32Array.BYTES_PER_ELEMENT);
    // 输出为:1 2 4
  2. 元素溢出

    • 有符号
      在【描述】列中,有符号的对象中有【补码】二字,是指如果数组中的元素超过了值范围应该如何处理
    js 复制代码
    console.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
       */
    • 无符号
      上边界溢出,取有效位;下边界溢出,转为等价无符号数
    js 复制代码
    console.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中也体现了TypedArrayArrayBuffer的关系

  1. ArrayBuffer实例化TypedArray

    js 复制代码
      const buffer = new ArrayBuffer(2);
      console.log(buffer);// ArrayBuffer { [Uint8Contents]: <00 00>, byteLength: 2 }
      const typedArray3 = new Int8Array(buffer);
  2. 读取ArrayBuffer

    js 复制代码
      console.log(typedArray3.buffer);// ArrayBuffer { [Uint8Contents]: <00 00>, byteLength: 2 }
  3. 修改ArrayBuffer

    js 复制代码
    typedArray3[0] = 100;
    console.log(typedArray3.buffer); // ArrayBuffer { [Uint8Contents]: <64 00>, byteLength: 2 }
    console.log(buffer); // ArrayBuffer { [Uint8Contents]: <64 00>, byteLength: 2 }

DataView

DataView 视图是一个可以从二进制 ArrayBuffer 对象中读写多种数值类型的底层接口,使用它时,不用考虑不同平台的字节序问题。

  1. 实例化DateView

    js 复制代码
    const 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);
  2. 修改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

  1. 创建Blob对象

    js 复制代码
    const obj = {hello: 'world'};
    const blob = new Blob([JSON.stringify(obj, null, 2)], {type : 'application/json'});
  2. 读取Blob对象的ArrayBuffer

    • 使用arrayBuffer方法

      js 复制代码
      blob.arrayBuffer().then(buffer => {
        console.log(buffer);
      });
    • 使用FileReader

      js 复制代码
      const reader = new FileReader();
      
      reader.addEventListener('loadend', () => {
          console.log(reader.result);
      });
      reader.readAsArrayBuffer(blob);
  1. 读取Blob对象所有内容的UTF-8 格式的字符串
    • 使用text方法

      js 复制代码
      blob.text().then(text => {
        console.log(text);
      });
    • 使用FileReader

      js 复制代码
      const reader2 = new FileReader();
      reader2.readAsText(blob);
      reader2.addEventListener('loadend', () => {
          console.log(reader2.result);
      });
  1. Blob对象转为base64
    使用FileReader

    js 复制代码
     const reader3 = new FileReader();
     reader3.readAsDataURL(blob);
     reader3.addEventListener('loadend', () => {
         console.log('readAsDataURL: ', reader3.result);
     });
  1. 生成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>

参考文章

相关推荐
真滴book理喻1 小时前
Vue(四)
前端·javascript·vue.js
程序员_三木1 小时前
Three.js入门-Raycaster鼠标拾取详解与应用
开发语言·javascript·计算机外设·webgl·three.js
开心工作室_kaic3 小时前
springboot476基于vue篮球联盟管理系统(论文+源码)_kaic
前端·javascript·vue.js
川石教育3 小时前
Vue前端开发-缓存优化
前端·javascript·vue.js·缓存·前端框架·vue·数据缓存
搏博3 小时前
使用Vue创建前后端分离项目的过程(前端部分)
前端·javascript·vue.js
温轻舟3 小时前
前端开发 之 12个鼠标交互特效上【附完整源码】
开发语言·前端·javascript·css·html·交互·温轻舟
web135085886353 小时前
2024-05-18 前端模块化开发——ESModule模块化
开发语言·前端·javascript
LCG元4 小时前
javascript页面设计案例【使用HTML、CSS和JavaScript创建一个基本的互动网页】
javascript
技术程序猿华锋5 小时前
Gemini 2.0 Flash 体验版实测:日常视觉识别的最佳选择,关键在于其API Key现在是免费调用
开发语言·javascript·ecmascript·googlecloud·gemini
TttHhhYy5 小时前
uniapp+vue开发app,蓝牙连接,蓝牙接收文件保存到手机特定文件夹,从手机特定目录(可自定义),读取文件内容,这篇首先说如何读取,手机目录如何寻找
开发语言·前端·javascript·vue.js·uni-app