当前位置:首页 > 文章列表 > 文章 > 前端 > JavaScript数组缓冲与视图使用教程

JavaScript数组缓冲与视图使用教程

2025-11-10 13:54:11 0浏览 收藏

“纵有疾风来,人生不言弃”,这句话送给正在学习文章的朋友们,也希望在阅读本文《JavaScript数组缓冲与视图应用详解》后,能够真的帮助到大家。我也会在后续的文章中,陆续更新文章相关的技术文章,有好的建议欢迎大家在评论留言,非常感谢!

JavaScript通过ArrayBuffer提供固定大小的原始二进制内存块,再借助TypedArray或DataView视图以特定类型和字节序读写数据,实现高效处理二进制流,广泛应用于WebSocket通信、文件解析等场景。

如何利用JavaScript的数组缓冲和视图处理二进制数据,以及它在网络通信或文件解析中的使用?

JavaScript处理二进制数据,其核心思想是提供一个原始的、固定大小的内存块——ArrayBuffer,它本身不包含任何格式信息。接着,通过TypedArray(如Uint8ArrayInt32Array)或DataView这些“视图”,我们才能以特定的数据类型和字节序来解读和操作这块内存。这套机制在网络通信中,比如WebSocket收发二进制消息,或是前端解析文件(如图片、音频、压缩包)时,扮演着至关重要的角色,它让浏览器端的JavaScript能够直接、高效地与底层字节流打交道,这感觉就像是打开了通往计算机更深层能力的一扇门。

解决方案

要高效地利用ArrayBuffer和视图处理二进制数据,我们首先要理解它们各自的职责。ArrayBuffer就像一块空白的画板,只提供内存空间,不关心你画什么。而TypedArrayDataView则是不同的画笔和眼镜,让你能以特定的方式去“看”和“操作”这块画板上的内容。

1. ArrayBuffer:原始内存块ArrayBuffer是一个固定长度的原始二进制数据缓冲区。它不能直接操作,因为它只是一个内存区域,没有提供任何读写方法。你需要通过它的视图来访问其内容。

const buffer = new ArrayBuffer(16); // 创建一个16字节的缓冲区
console.log(buffer.byteLength); // 16

2. TypedArray:类型化数组视图TypedArrayArrayBuffer最常见的视图。它将ArrayBuffer中的字节解释为特定类型的数值数组。例如,Uint8Array将每个字节视为一个无符号8位整数,而Int32Array则将每四个字节视为一个有符号32位整数。

const buffer = new ArrayBuffer(16);
const uint8View = new Uint8Array(buffer); // 创建一个Uint8Array视图
uint8View[0] = 255;
uint8View[1] = 128;
console.log(uint8View); // Uint8Array [255, 128, 0, 0, ...]

const int32View = new Int32Array(buffer); // 创建一个Int32Array视图
int32View[0] = 123456789; // 写入一个32位整数,会占据前4个字节
console.log(int32View); // Int32Array [123456789, 0, 0, 0]
console.log(uint8View); // Uint8Array [21, 205, 91, 7, 0, ...] (原始字节内容已改变)

TypedArray在处理同类型数据序列时非常高效,比如图像的像素数据或音频的采样数据。

3. DataView:灵活的字节视图DataView提供了一种更精细、更灵活的方式来操作ArrayBuffer。它允许你在任意字节偏移量处读写不同大小和字节序(大端序/小端序)的数值。这在处理混合数据类型或需要严格控制字节序的协议时尤其有用。

const buffer = new ArrayBuffer(8); // 8字节缓冲区
const dataView = new DataView(buffer);

// 写入一个无符号8位整数
dataView.setUint8(0, 0x41); // 偏移量0,写入字符'A'的ASCII值

// 写入一个大端序的无符号16位整数
dataView.setUint16(1, 0x4243, false); // 偏移量1,写入0x4243 (大端序)

// 写入一个小端序的浮点数
dataView.setFloat32(3, 3.14159, true); // 偏移量3,写入3.14159 (小端序)

console.log(new Uint8Array(buffer)); // 看看原始字节

// 读取数据
console.log(dataView.getUint8(0)); // 65 (0x41)
console.log(dataView.getUint16(1, false)); // 16963 (0x4243)
console.log(dataView.getFloat32(3, true)); // 3.141590118408203

DataViewgetset方法都有一个可选的littleEndian参数,默认为false(大端序),这对于跨平台或特定协议的字节序兼容性至关重要。

在网络通信或文件解析中的应用:

  • 网络通信 (WebSocket): WebSocket API可以直接发送和接收ArrayBuffer。发送时,socket.send(buffer)即可。接收时,event.data如果是二进制数据,就会是ArrayBuffer类型,你可以直接对其创建TypedArrayDataView进行解析。
  • 文件解析 (FileReaderfetch):
    • 本地文件: 使用FileReaderreadAsArrayBuffer()方法可以将用户选择的文件读取为一个ArrayBuffer
    • 远程文件: 使用fetch API获取二进制数据时,response.arrayBuffer()方法会返回一个Promise,解析后就是ArrayBuffer。 一旦获取到ArrayBuffer,就可以根据文件格式规范(如JPEG、WAV、ZIP等)使用DataView在特定偏移量读取文件头、元数据和实际数据块。

为什么在处理二进制数据时,我们不能直接使用普通的JavaScript数组?

这是一个很好的问题,而且我个人觉得,理解这一点是深入掌握ArrayBuffer的关键。简单来说,JavaScript的普通数组(Array)设计之初就不是为了直接操作原始二进制数据。它们是高度抽象的、动态的、异构的集合。

一个普通的JavaScript数组,比如[1, "hello", {a: 1}],可以存储任何类型的值,而且长度是可变的。当你存储数字时,这些数字在底层可能被优化存储,但它们仍然是JavaScript的Number类型,是浮点数表示,而不是像C语言中intchar那样的固定位宽整数。这意味着:

  1. 内存布局不确定性: 普通数组中的元素在内存中不一定是连续存储的,或者说,即使数字连续,它们也不是原始字节。每个元素都可能是一个指向实际数据(如对象、字符串、或封装的数字)的指针。这使得我们无法像操作底层内存那样,以字节为单位进行精确的偏移量计算和读取。
  2. 性能开销: 每次访问或修改普通数组的元素,JavaScript引擎都需要进行类型检查、装箱/拆箱操作(将原始数字转换为Number对象,反之亦然),以及潜在的垃圾回收管理。这对于需要大量、高速处理数字序列(如图像像素、音频采样)的场景来说,性能开销巨大。
  3. 缺乏类型化视图: 普通数组无法提供“以8位无符号整数序列看待这块内存”或“以32位浮点数序列看待这块内存”的能力。ArrayBufferTypedArray正是为了填补这个空白而生,它们提供了一种直接映射到底层内存的方式,其操作几乎可以达到原生语言的效率。
  4. 与底层API的互操作性: 许多Web API(如WebSocket、WebRTC、WebGL、WebAudio、File API)在处理二进制数据时,都期望或返回ArrayBuffer。如果你试图用普通数组去对接这些API,你会发现要么行不通,要么需要大量的手动转换,效率低下且容易出错。

所以,当我们需要进行字节级别的操作,或者与那些需要原始二进制数据的Web API交互时,ArrayBufferTypedArray是不可替代的。它们提供了一种桥梁,让JavaScript能够以一种高效且可控的方式,触及到更接近硬件的层面。

在网络通信中,如何有效地发送和接收二进制数据?

在现代Web应用中,有效地发送和接收二进制数据是实现高性能、富交互体验的关键。这通常涉及到与服务器进行更底层的数据交换,而不是传统的JSON或文本。

1. 使用WebSocket进行实时二进制通信:

WebSocket是进行双向、全双工通信的首选,它原生支持发送和接收二进制数据。

  • 发送: 你可以直接将一个ArrayBufferTypedArray实例发送出去。

    const ws = new WebSocket('ws://localhost:8080');
    
    ws.onopen = () => {
        const buffer = new ArrayBuffer(4);
        const view = new Uint8Array(buffer);
        view[0] = 0x01; // 消息类型
        view[1] = 0x02; // 命令
        view[2] = 0x03; // 数据
        view[3] = 0x04;
    
        ws.send(buffer); // 直接发送ArrayBuffer
        // 也可以发送TypedArray: ws.send(view);
    };

    服务器端收到后,会将其作为二进制数据处理。

  • 接收: 当WebSocket接收到二进制消息时,event.data将是一个ArrayBuffer实例。

    ws.onmessage = (event) => {
        if (event.data instanceof ArrayBuffer) {
            console.log('收到二进制数据:', event.data);
            const dataView = new DataView(event.data);
            const messageType = dataView.getUint8(0);
            const payloadLength = dataView.getUint16(1, false); // 假设第二个字节开始是长度,大端序
    
            // 根据消息类型和长度解析后续数据
            // ...
        } else {
            console.log('收到文本数据:', event.data);
        }
    };

    这里需要注意的是,你可能需要设置ws.binaryType = 'arraybuffer';来确保接收到的二进制数据是ArrayBuffer类型(通常是默认值)。

2. 使用Fetch API发送和接收二进制数据:

Fetch API是现代浏览器中进行HTTP请求的首选。它也可以处理二进制数据,尤其适合文件上传或下载。

  • 发送 (例如,上传文件或二进制流): 你可以将ArrayBufferBlob作为body发送。

    const fileInput = document.getElementById('fileInput');
    fileInput.addEventListener('change', async (event) => {
        const file = event.target.files[0];
        if (file) {
            const arrayBuffer = await file.arrayBuffer(); // 将文件读取为ArrayBuffer
    
            fetch('/upload-binary', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/octet-stream' // 声明为二进制流
                },
                body: arrayBuffer // 直接发送ArrayBuffer
            })
            .then(response => response.json())
            .then(data => console.log('上传成功:', data))
            .catch(error => console.error('上传失败:', error));
        }
    });
  • 接收 (例如,下载图片、音频或自定义二进制文件):Response对象提供了arrayBuffer()方法来将响应体解析为ArrayBuffer

    fetch('/get-binary-data')
        .then(response => {
            if (!response.ok) {
                throw new Error('Network response was not ok');
            }
            return response.arrayBuffer(); // 解析为ArrayBuffer
        })
        .then(buffer => {
            console.log('下载的二进制数据:', buffer);
            const dataView = new DataView(buffer);
            // 进一步解析,比如判断文件类型,或者处理自定义协议
            // ...
        })
        .catch(error => console.error('下载失败:', error));

关键考虑点:

  • 协议设计: 当你自定义二进制协议时,设计一个清晰的消息头(包含消息类型、长度、版本等)至关重要。DataView在这里能帮助你精确地读写这些头部信息。
  • 字节序 (Endianness): 这是跨平台通信中的一个常见陷阱。不同的CPU架构可能使用不同的字节序(大端序或小端序)。务必在协议中明确规定字节序,并在使用DataViewget/set方法时,正确设置littleEndian参数。例如,dataView.getUint32(0, true)表示以小端序读取32位无符号整数。
  • 分块与流式处理: 对于非常大的文件或数据流,你可能不想一次性加载到内存中。Web Streams API(配合fetch)允许你以流式方式处理响应,这对于内存管理和用户体验都很有益。

有效地处理二进制数据,不仅仅是技术层面的操作,更是一种对数据传输效率和底层机制的深刻理解。这使得我们能够构建出更强大、更高效的Web应用。

解析文件(如图片、音频)时,ArrayBuffer和DataView扮演了什么角色?

在前端解析文件,尤其是二进制格式的文件(如图片、音频、视频、PDF等),ArrayBufferDataView简直是不可或缺的工具。它们为JavaScript提供了一双“透视眼”和一双“手术刀”,让我们能够直接深入文件的字节流,理解其内部结构。

1. 获取文件的ArrayBuffer

首先,无论是用户上传的本地文件,还是从网络下载的远程文件,我们都需要将其内容获取为ArrayBuffer

  • 本地文件: 使用FileReader API。

    const fileInput = document.getElementById('fileInput');
    fileInput.addEventListener('change', (event) => {
        const file = event.target.files[0];
        if (file) {
            const reader = new FileReader();
            reader.onload = (e) => {
                const arrayBuffer = e.target.result; // 这里就是ArrayBuffer
                console.log('文件ArrayBuffer:', arrayBuffer);
                // 接下来就可以用DataView解析了
                parseFile(arrayBuffer);
            };
            reader.readAsArrayBuffer(file); // 以ArrayBuffer形式读取文件
        }
    });
  • 远程文件: 使用fetch API。

    async function downloadAndParseRemoteFile(url) {
        try {
            const response = await fetch(url);
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            const arrayBuffer = await response.arrayBuffer(); // 获取ArrayBuffer
            console.log('远程文件ArrayBuffer:', arrayBuffer);
            parseFile(arrayBuffer);
        } catch (error) {
            console.error('下载或解析文件失败:', error);
        }
    }
    
    // downloadAndParseRemoteFile('path/to/your/image.jpg');

2. DataView的解析魔法:

一旦有了ArrayBufferDataView就登场了。文件格式通常都有一个严格的二进制结构:文件头(magic number)、元数据(尺寸、编码、采样率等)、以及实际的数据块。DataView允许我们精确地读取这些结构化信息。

以解析一个简单的WAV音频文件为例(简化版):

WAV文件通常以一个RIFF块开始,接着是WAVE格式块,然后是fmt子块(包含音频格式信息),最后是data子块(包含实际音频数据)。

function parseWAVHeader(arrayBuffer) {
    const dataView = new DataView(arrayBuffer);
    let offset = 0;

    // 读取RIFF块
    const riffChunkId = String.fromCharCode(dataView.getUint8(offset++), dataView.getUint8(offset++), dataView.getUint8(offset++), dataView.getUint8(offset++)); // "RIFF"
    const fileSize = dataView.getUint32(offset, true); // 文件大小,小端序
    offset += 4;
    const waveFormat = String.fromCharCode(dataView.getUint8(offset++), dataView.getUint8(offset++), dataView.getUint8(offset++), dataView.getUint8(offset++)); // "WAVE"
    offset += 4; // 跳过"WAVE"

    // 读取fmt子块
    const fmtChunkId = String.fromCharCode(dataView.getUint8(offset++), dataView.getUint8(offset++), dataView.getUint8(offset++), dataView.getUint8(offset++)); // "fmt "
    const fmtChunkSize = dataView.getUint32(offset, true); // fmt块大小
    offset += 4;
    const audioFormat = dataView.getUint16(offset, true); // 音频格式 (1 = PCM)
    offset += 2;
    const numChannels = dataView.getUint16(offset, true); // 声道数
    offset += 2;
    const sampleRate = dataView.getUint32(offset, true); // 采样率
    offset += 4;
    const byteRate = dataView.getUint32(offset, true); // 字节率
    offset += 4;
    const blockAlign = dataView.getUint16(offset, true); // 块对齐
    offset += 2;
    const bitsPerSample = dataView.getUint16(offset, true); // 每采样位数
    offset += 2;

    // 假设我们已经解析了fmt块,现在跳过剩余的fmt块数据
    offset += (fmtChunkSize - 16); // 16是fmt块固定部分的大小

    // 查找data子块(可能会有其他块在中间,需要循环查找)
    let dataChunkId = '';
    let dataChunkSize = 0;
    while (offset < arrayBuffer.byteLength - 8) { // 确保还有足够的空间读取ID和大小
        dataChunkId = String.fromCharCode(dataView.getUint8(offset++), dataView.getUint8(offset++), dataView.getUint8(offset++), dataView.getUint8(offset++));
        dataChunkSize = dataView.getUint32(offset, true);
        offset += 4;

        if (dataChunkId === 'data') {
            break;
        }
        offset += dataChunkSize; // 跳过当前块
    }


    console.log('--- WAV Header Info ---');
    console.log(`RIFF ID: ${riffChunkId}`);
    console.log(`File Size: ${fileSize} bytes`);
    console.log(`WAVE Format: ${waveFormat}`);
    console.log(`FMT ID: ${fmtChunkId}`);
    console.log(`Audio Format: ${audioFormat} (1=PCM)`);
    console.log(`Channels: ${numChannels}`);
    console.log(`Sample Rate: ${sampleRate} Hz`);
    console.log(`Bits per Sample: ${bitsPerSample}`);
    console.log(`Data Chunk ID: ${dataChunkId}`);
    console.log(`Data Chunk Size: ${dataChunkSize} bytes`);
    console.log(`Audio Data Starts at Offset: ${offset}`);

    // 音频数据部分可以创建一个新的TypedArray视图
    const audioData = new Int16Array(arrayBuffer, offset, dataChunkSize / (bitsPerSample / 8));
    // console.log('First 10 audio samples:', audioData.slice(0, 10));

    return {

理论要掌握,实操不能落!以上关于《JavaScript数组缓冲与视图使用教程》的详细介绍,大家都掌握了吧!如果想要继续提升自己的能力,那么就来关注golang学习网公众号吧!

ES6尾调用优化如何实现?ES6尾调用优化如何实现?
上一篇
ES6尾调用优化如何实现?
天猫双11红包领取技巧与红包雨时间
下一篇
天猫双11红包领取技巧与红包雨时间
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ChatExcel酷表:告别Excel难题,北大团队AI助手助您轻松处理数据
    ChatExcel酷表
    ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
    3164次使用
  • Any绘本:开源免费AI绘本创作工具深度解析
    Any绘本
    探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
    3376次使用
  • 可赞AI:AI驱动办公可视化智能工具,一键高效生成文档图表脑图
    可赞AI
    可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
    3405次使用
  • 星月写作:AI网文创作神器,助力爆款小说速成
    星月写作
    星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
    4509次使用
  • MagicLight.ai:叙事驱动AI动画视频创作平台 | 高效生成专业级故事动画
    MagicLight
    MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
    3785次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码