前端进阶:通过自定义JSON方法实现二进制数据的高效传输

311 阅读3分钟

一、问题背景与原生局限

JavaScript的JSON.stringify()JSON.parse()在处理特殊数据结构时存在明显局限,特别是面对类型化数组(TypedArray)时。直接序列化会得到无意义的空对象:

const arr = new Uint8Array([1,2,3]);
console.log(JSON.stringify(arr)); // {"0":1,"1":2,"2":3}

这种结果不仅丢失了数组类型信息,也无法正确反序列化还原。这在处理二进制数据、WebGL缓冲区等场景时会造成严重问题。

二、核心解决方案设计

要实现完整的类型化数组序列化,需要三个关键步骤:

  1. 类型标记:为每个TypedArray添加类型标识
  2. 数据编码:将二进制数据转换为Base64字符串
  3. 自定义解析:通过reviver函数重建原始对象

image.png

三、核心代码实现解析

3.1 类型化数组转Base64

_typedArrayToBase64(typedArray) {
  const bytes = new Uint8Array(typedArray.buffer);
  let binary = '';
  for (let i = 0; i < bytes.byteLength; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}

通过遍历字节数据构建二进制字符串,最终使用btoa转换为Base64编码。

3.2 Base64转类型化数组

_base64ToTypedArray(base64String, TypedArrayConstructor) {
  const binaryString = atob(base64String);
  const bytes = new Uint8Array(binaryString.length);
  for (let i = 0; i < bytes.length; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }
  return new TypedArrayConstructor(bytes.buffer);
}

逆向操作使用atob解码,重建字节缓冲区后创建对应类型的数组。

3.3 原型方法扩展

typedArrayConstructors.forEach(TypedArray => {
  if (!TypedArray.prototype.toJSON) {
    TypedArray.prototype.toJSON = function() {
      return {
        __type: TypedArray.name,
        data: this._typedArrayToBase64(this)
      };
    }
  }
});

为所有类型化数组添加toJSON方法,在序列化时自动触发转换逻辑。

四、完整实战案例演示

const TypedArrayJSON = {
    // 辅助函数:类型化数组到 Base64
    _typedArrayToBase64(typedArray) {
        const buffer = typedArray.buffer;
        const bytes = new Uint8Array(buffer);
        let binary = '';
        for (let i = 0; i < bytes.byteLength; i++) {
            binary += String.fromCharCode(bytes[i]);
        }
        return btoa(binary);
    },

    // 辅助函数:Base64 到类型化数组
    _base64ToTypedArray(base64String, TypedArrayConstructor) {
        const binaryString = atob(base64String);
        const len = binaryString.length;
        const bytes = new Uint8Array(len);
        for (let i = 0; i < len; i++) {
            bytes[i] = binaryString.charCodeAt(i);
        }
        return new TypedArrayConstructor(bytes.buffer);
    },

    // 初始化:为 Typed Array 添加 toJSON 方法
    _init() {
        const typedArrayConstructors = [Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, BigInt64Array, BigUint64Array, Float32Array, Float64Array];

        typedArrayConstructors.forEach(TypedArray => {
            if (!TypedArray.prototype.toJSON) {
                TypedArray.prototype.toJSON = function() {
                    return {
                        __type: TypedArray.name,
                        data: TypedArrayJSON._typedArrayToBase64(this)// 使用 this 访问对象内部方法
                    };
                }
                ;
            }
        }
        );
    },

    // 自定义 replacer 函数 (stringify 使用)
    _typedArrayReplacer(key, value) {
        if (value && typeof value === 'object' && value.__type && value.data) {
            return value;
        }
        return value;
    },

    // 自定义 reviver 函数 (parse 使用)
    _typedArrayReviver(key, value) {
        if (value && typeof value === 'object' && value.__type && value.data) {
            switch (value.__type) {
            case 'Int8Array':
                return TypedArrayJSON._base64ToTypedArray(value.data, Int8Array);
            case 'Uint8Array':
                return TypedArrayJSON._base64ToTypedArray(value.data, Uint8Array);
            case 'Uint8ClampedArray':
                return TypedArrayJSON._base64ToTypedArray(value.data, Uint8ClampedArray);
            case 'Int16Array':
                return TypedArrayJSON._base64ToTypedArray(value.data, Int16Array);
            case 'Uint16Array':
                return TypedArrayJSON._base64ToTypedArray(value.data, Uint16Array);
            case 'Int32Array':
                return TypedArrayJSON._base64ToTypedArray(value.data, Int32Array);
            case 'Uint32Array':
                return TypedArrayJSON._base64ToTypedArray(value.data, Uint32Array);
            case 'BigInt64Array':
                return TypedArrayJSON._base64ToTypedArray(value.data, BigInt64Array);
            case 'BigUint64Array':
                return TypedArrayJSON._base64ToTypedArray(value.data, BigUint64Array);
            case 'Float32Array':
                return TypedArrayJSON._base64ToTypedArray(value.data, Float32Array);
            case 'Float64Array':
                return TypedArrayJSON._base64ToTypedArray(value.data, Float64Array);
            default:
                return value;
            }
        }
        return value;
    },

    // 增强的 JSON.stringify
    stringify(obj) {
        return JSON.stringify(obj, this._typedArrayReplacer);
        // 使用 this 访问对象内部方法
    },

    // 增强的 JSON.parse
    parse(str) {
        return JSON.parse(str, this._typedArrayReviver);
        // 使用 this 访问对象内部方法
    }
};

// 初始化 TypedArrayJSON 对象(添加 toJSON 方法)
TypedArrayJSON._init();
// 创建测试数据
const complexData = {
  buffer: new Uint8Array([72, 101, 108, 108, 111]), // "Hello"的ASCII
  floatArray: new Float32Array([1.1, 2.2, 3.3]),
  timestamp: Date.now()
};

// 序列化
const jsonStr = TypedArrayJSON.stringify(complexData);
console.log(jsonStr);
// 输出结果:
// {
//   "buffer":{"__type":"Uint8Array","data":"SGVsbG8="},
//   "floatArray":{"__type":"Float32Array","data":"AACAPwAAAEAAAMCC"},
//   "timestamp":1717986912345
// }

// 反序列化
const parsedData = TypedArrayJSON.parse(jsonStr);
console.log(parsedData.floatArray instanceof Float32Array); // true
console.log(new TextDecoder().decode(parsedData.buffer)); // "Hello"

五、方案优势与注意事项

核心优势:

  1. 完整保留类型信息
  2. 数据体积优化(Base64相比JSON数组更紧凑)
  3. 支持所有标准类型化数组
  4. 完美兼容现有JSON工作流

使用注意事项:

  1. 需在代码初始化时调用_init()方法
  2. BigInt类型需要额外处理
  3. 注意数据安全(Base64非加密编码)
  4. 服务端需要对应解析逻辑

六、扩展应用场景

本方案可应用于:

  • WebSocket二进制数据传输
  • IndexedDB存储二进制数据
  • Web Worker间大数据传输
  • Canvas/WebGL图像数据处理
  • 科学计算数据持久化

通过自定义JSON序列化方案,我们成功突破了JavaScript原生能力的限制,为复杂数据结构的处理提供了标准化解决方案。这种模式也可扩展到其他特殊数据类型(如Date、RegExp等),为现代Web应用开发提供强有力的支撑。