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

后端(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>

参考文章

相关推荐
我命由我123451 小时前
React Router 6 - 编程式路由导航、useInRouterContext、useNavigationType
前端·javascript·react.js·前端框架·html·ecmascript·js
橙露2 小时前
JavaScript 异步编程:Promise、async/await 从原理到实战
开发语言·javascript·ecmascript
我命由我123453 小时前
React Router 6 - 嵌套路由、路由传递参数
前端·javascript·react.js·前端框架·html·ecmascript·js
十六年开源服务商3 小时前
2026年WordPress网站地图完整指南
java·前端·javascript
英俊潇洒美少年4 小时前
MessageChannel 如何实现时间切片
javascript·react.js·ecmascript
技术钱5 小时前
react数据大屏四种适配方案
javascript·react.js·ecmascript
李明卫杭州5 小时前
JavaScript 严格模式下 arguments 的区别
前端·javascript
一次旅行6 小时前
今日心理学知识分享(三)
开发语言·javascript·程序人生·ecmascript
牛十二6 小时前
openclaw安装mcporter搜索小红书
开发语言·javascript·ecmascript
小金鱼Y6 小时前
🔥 前端人必看:浏览器安全核心知识点全解析(XSS/CSRF/DDoS)
前端·javascript·安全