做了这么多年前端,踩过最多的坑就是在处理二进制数据这块。尤其是最近做一个Web端音视频录制项目时,需要把麦克风采集到的音频帧转成字节流再推到后端,ArrayBuffer、DataView、Uint8Array这几个API算是彻底用了个遍。网上关于这几个概念的文章不少,但大多是各讲各的,很少有人把“为什么要用视图”“字节序到底怎么选”这些问题讲通。这篇文章就从一个实际项目的视角,把我踩过的坑、验证过的方案一次说清楚,顺便把Blob和ArrayBuffer之间来回折腾的那点事也交代明白。
这套东西适合谁?如果你写前端,迟早要跟文件上传、图片压缩、音视频处理、WebSocket二进制协议打交道;如果你写Node.js,Buffer本质上就是Uint8Array的子类;甚至如果你在做小程序、Electron、Web Worker,这套知识一样绕不开。准备好,我们直接开讲。
提示:文章里的“Uint8Array”很多人会拼成“Unit8Array”,少一个i。这不是小事,拼错了编辑器直接给你报错,我还真见过有人因为这个误以为项目环境出了问题。
1. 先搞明白ArrayBuffer到底是个什么“Buffer”
1.1 一个真实需求:文件解析和网络传输都离不开二进制
在说概念之前,先从一个实际的场景入手。我做音视频录制项目时,需要把麦克风采集到的PCM音频数据通过WebSocket实时发送到服务端。这条路走下来,你会发现你手里的数据根本不是普通对象,也不是字符串,而是一堆字节。图片、音频、视频、加密消息、协议包头,本质上全是二进制数据。在后端眼里,前端传过来的所有文件、所有流,就是连续的一串字节。
可是JavaScript这门语言天生就不擅长直接操作字节。JS的历史包袱是HTML文档和DOM,数组里随便放什么类型都行,字符串靠UTF-16编码,跟“底层”“二进制”这些词八竿子打不着。浏览器为了弥补这个短板,才在一众ECMAScript规范之外引入了ArrayBuffer、DataView、TypedArray这一整套二进制数据处理体系。ArrayBuffer解决的是“怎么存储二进制数据”,DataView和Uint8Array解决的是“怎么读写这些数据”。
1.2 ArrayBuffer的本质:定长、连续、原始的字节“仓库”
ArrayBuffer简单说就是一段由JavaScript引擎分配好的连续内存,里面只存字节,每个字节8位,范围就是0到255。它的长度在创建那一刻就固定了,不能直接扩容。你可以把它理解成一块被围起来的空地,圈定了面积,但里面是毛坯的,什么都没有规划。
javascript复制const buffer = new ArrayBuffer(16);
console.log(buffer.byteLength); // 16
上面这段代码申请了16个字节的内存。注意,这时候buffer里面是什么?全是大坑里的零。每一位都是0。ArrayBuffer本身不区分“这里是一个整数”“那里是一个浮点数”,它就是空白的存储空间。和普通数组最大的区别是:数组是“对象集合”,ArrayBuffer是“原始内存”。
还有一个很关键的点:ArrayBuffer不能直接通过下标访问。你写buffer[0]得到的不是第一个字节的值,而是undefined。这一点曾经让不少新手误以为ArrayBuffer就是个空壳子,其实不是——它是故意不让你直接访问的,原因后面说。
1.3 为什么ArrayBuffer不能直接读写
ArrayBuffer不能直接读写,这不是设计缺陷,而是刻意设计。原因很简单:一段连续的内存,你拿到它时,并不知道里面存的是什么类型的数据。同一段内存,如果按无符号8位整数去读,和按32位浮点数去读,读出来的结果天差地别。ArrayBuffer自己也不知道,所以它把“怎么解释这段内存”的活交给了视图(View)。
打个比方:ArrayBuffer是一块地皮,你要在上面盖楼、修路还是建公园,需要拿到对应的图纸和批文,不能直接上手刨地。TypedArray和DataView就是这些图纸。
实际项目里,ArrayBuffer最常见的来源有三个:FileReader.readAsArrayBuffer()读文件、Response.arrayBuffer()读网络请求、AudioContext里拿音频数据。拿到手之后,你就得想下一步:这份二进制数据到底该怎么解读。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 视图机制:给字节“仓库”装不同规格的货架
2.1 同一个ArrayBuffer,不同视图读出完全不同的结果
视图的核心是“解释”。同一个ArrayBuffer,你可以把它整体映射成一个Uint8Array,每个元素1字节;也可以映射成Uint16Array,每个元素2字节;甚至映射成Float64Array,每个元素8字节。这几种映射方式读出来的内容完全不同。
javascript复制const buffer = new ArrayBuffer(4);
const uint8 = new Uint8Array(buffer);
const uint16 = new Uint16Array(buffer);
uint8[0] = 0x12;
uint8[1] = 0x34;
console.log(uint8[0].toString(16)); // 12
console.log(uint8[1].toString(16)); // 34
console.log(uint16[0].toString(16)); // 3412(小端平台的输出)
同一个buffer,用Uint8Array看是两个字
