likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 14

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

二进制数

  1. 什么是二进制?

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

  2. 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]) 方法可以返回数字的指定进制的字符串形式
    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
    // 创建一个n个字节的缓冲区
    const buffer = new ArrayBuffer(2);
    // byteLength为字节长度,内容会被初始化为0
    console.log(buffer); 
    
    // nodejs执行后输出:ArrayBuffer { [Uint8Contents]: <00 00>, byteLength: 2 }
    
  3. 不能直接操作 ArrayBuffer 中的内容,而是要通过TypedArray或 DataView 对象来操作。它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容

TypedArray

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

子类

完整见MDN 前端怎么处理二进制数据?

  1. 字节大小 这里的字节大小指的是单个元素的字节大小,有静态方法可以获取:
    console.log(Int8Array.BYTES_PER_ELEMENT, Int16Array.BYTES_PER_ELEMENT, Int32Array.BYTES_PER_ELEMENT);
    // 输出为:1 2 4
    
  2. 元素溢出
    • 有符号 在【描述】列中,有符号的对象中有【补码】二字,是指如果数组中的元素超过了值范围应该如何处理
    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
       */
    
    • 无符号 上边界溢出,取有效位;下边界溢出,转为等价无符号数
    console.log(new Uint8Array([300])); // Uint8Array(1) [ 44 ]
    // 300的二进制数是:100101100
    // 长度溢出,截取8位:00101100
    // 对应十进制数:44
    
    console.log(new Uint8Array([-1])); // Uint8Array(1) [ 255 ]
    

API

有基本的数组的方法,比如join,也有一些特有的属性,比如byteLength

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
      const buffer = new ArrayBuffer(2);
      console.log(buffer);// ArrayBuffer { [Uint8Contents]: <00 00>, byteLength: 2 }
      const typedArray3 = new Int8Array(buffer);
    
  2. 读取ArrayBuffer
      console.log(typedArray3.buffer);// ArrayBuffer { [Uint8Contents]: <00 00>, byteLength: 2 }
    
  3. 修改ArrayBuffer
    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

    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

    // 在视图开始的指定字节偏移处存储一个带符号 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对象
    const obj = {hello: 'world'};
    const blob = new Blob([JSON.stringify(obj, null, 2)], {type : 'application/json'});
    
    
  2. 读取Blob对象的ArrayBuffer
    • 使用arrayBuffer方法
      blob.arrayBuffer().then(buffer => {
        console.log(buffer);
      });
      
    • 使用FileReader
      const reader = new FileReader();
      
      reader.addEventListener('loadend', () => {
          console.log(reader.result);
      });
      reader.readAsArrayBuffer(blob);
      

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

  1. 读取Blob对象所有内容的UTF-8 格式的字符串
    • 使用text方法

      blob.text().then(text => {
        console.log(text);
      });
      
    • 使用FileReader

      const reader2 = new FileReader();
      reader2.readAsText(blob);
      reader2.addEventListener('loadend', () => {
          console.log(reader2.result);
      });
      

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

  1. Blob对象转为base64 使用FileReader
     const reader3 = new FileReader();
     reader3.readAsDataURL(blob);
     reader3.addEventListener('loadend', () => {
         console.log('readAsDataURL: ', reader3.result);
     });
    

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

  1. 生成Blob对象的内存URL 使用 URL.createObjectURL,生成的url可以预览、下载文件
        <!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 的功能并将其扩展以支持用户系统上的文件。

<!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(下载的链接)

<!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>

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

参考文章