前端也要学的“文件转换规则知识

258 阅读8分钟

image.png

Text 作为起点

Text 至 Blob

const blob = new Blob(["Hello, world!"], {
    type: "text/plain"
});

console.log(blob)

Text 至 File

const file = new File(["Hello, world!"], "hello.txt", {
    type: "text/plain"
});

console.log(file)

Text 至 URL

encodeURIComponent 按照 Unicode字符集 将所有字符进行解码,再把每个字节从二进制转为十六进制表示,也就是两个16进制字符表示一个字节,最后在每个字节的前面加上 % 输出。

const arrayBuffer = new TextEncoder().encode(`我`)
const encodedStr = Array.from(arrayBuffer)
    .map(i => Number(i).toString(16))
    .map(i => i.toUpperCase())
    .join("%")
    
console.log(encodedStr);  // E6%88%91
encodeURIComponent(`我`) // %E6%88%91

encodeURIComponent 编码的字符范围比 encodeURI 更广,/?:等都可以进行编码。

encodeURIComponent(`http://www.baidu.com?name=我&age=18`)
// http%3A%2F%2Fwww.baidu.com%3Fname%3D%E6%88%91%26age%3D18

encodeURI(`http://www.baidu.com?name=我&age=18`)
// http://www.baidu.com?name=%E6%88%91&age=18

注意:

  • encodeURIComponent 配合 decodeURIComponent使用。
  • encodeURI 配合 decodeURI使用。
  • 对URL进行转码时候,可以采用 encodeURIComponent ,更加的全面。

Text 至 Base64

0-255以内的字符,属于 Latin1 字符集

const text = "Hello World";

console.log(window.btoa(text));
  1. 先把 "hello world!"Latin1编码 逐字符字节流。

    ['01001000', '01100101', '01101100', '01101100', '01101111', '00100000', '01010111', '01101111', '01110010', '01101100', '01100100']
    
  2. 再把这串字节流按 Base64编码 转成可打印文本。

    SGVsbG8gV29ybGQ=
    

注意:btoa只支持 Latin1编码 的字符,对于 非 Latin1编码 的字符会抛出错误。

window.btoa("你好!")

包含中文、特殊字符等,属于 Unicode 字符集

encodeURIComponent
const text = "w爱n";
let encodedString  = encodeURIComponent(text);

console.log(window.btoa(encodedString));
  1. 先通过 UTF-8编码 转为 字节流。
[119, 231, 136, 177, 110]
  1. 将字节流转为 16 进制字符。
  ['77', 'e7', '88', 'b1', '6e']
  1. 字符通过用%接成字符串。
77%E7%88%B1%6E
  1. 先把 77%E7%88%B1%6ELatin1编码 逐字符字节流。
['00110111', '00110111', '00100101', '01000101', '00110111', '00100101', '00111000', '00111000', '00100101', '01000010', '00110001', '00100101', '00110110', '01000101']
  1. 再把这串字节流按 Base64编码 转成可打印文本。
NzclRTclODglQjElNkU=
TextEncoder 配合 fromCharCode
const text = "w爱n";
const encodedBuffer = new TextEncoder().encode(text)
let characters = [];
for (let i = 0; i < encodedBuffer.length; i++) {
    characters.push(String.fromCharCode(encodedBuffer[i]));
}
const concatenatedString = characters.join('')

console.log(window.btoa(concatenatedString));

FileReader

const text = "w爱n";
const file = new File([text], "text.txt", { type: "text/plain" });
const render = new FileReader();
render.readAsDataURL(file);
render.onload = function () {
    console.log(render.result);
}

Text 至 ArrayBuffer

方案一:只处理ASCII码表字符

/**
 * 将字符串转换为字节缓冲(仅支持单字节 ASCII)
 * 约束:
 * - 每个字符的码点必须 <= 0xFF(单字节)
 * - 若包含多字节字符(如中文、emoji),结果将错误或不可靠
 * @param {string} str - 仅含单字节 ASCII 的字符串
 * @return {ArrayBuffer} 字节缓冲
 */
function string2ArrayBuffer(str) {
    let val = '';
    for (let i = 0; i < str.length; i++) {
        // 取当前字符的 UTF-16 码单元(0-65535),并转为十六进制字符串
        if (val === '') {
            val = str.charCodeAt(i).toString(16);
        } else {
            // 继续拼接下一个字符的十六进制表示,用逗号分隔
            val += ',' + str.charCodeAt(i).toString(16);
        }
    }

    // 将16进制转化为ArrayBuffer
    // 通过正则匹配每两个十六进制字符(一个字节),再用 parseInt 转为 0-255 的数值
    // 用这些数值构造 Uint8Array,并返回其底层的 ArrayBuffer
    return new Uint8Array(
        val.match(/[\da-f]{2}/gi).map(function (h) {
            return parseInt(h, 16);
        })
    ).buffer;
}

console.log(string2ArrayBuffer('hello'));

方案二:TextEncoder转换(微信小程序不支持)

const encoder = new TextEncoder();
console.log(encoder.encode("我i你").buffer);

方案三:处理全部字符

/**
 * 将字符串转换为 UTF-8 编码的 ArrayBuffer
 * @param {string} str - 输入字符串
 * @return {ArrayBuffer} 转换后的 ArrayBuffer
 */
function string2ArrayBuffer(str) {
  let len = str.length;
  let bytes = 0;

  // 计算需要的字节数
  for (let i = 0; i < len; i++) {
    const code = str.charCodeAt(i);
    if (code < 0x007F) {
      bytes += 1;
    } else if (code < 0x07FF) {
      bytes += 2;
    } else if (code < 0xFFFF) {
      bytes += 3;
    } else {
      bytes += 4;
    }
  }

  // 分配缓冲区
  const buffer = new ArrayBuffer(bytes);
  const uint8 = new Uint8Array(buffer);
  let offset = 0;

  // 填充字节
  for (let i = 0; i < len; i++) {
    const code = str.charCodeAt(i);
    if (code < 0x007F) {
      uint8[offset++] = code;
    } else if (code < 0x07FF) {
      uint8[offset++] = 0xC0 | (code >>> 6);
      uint8[offset++] = 0x80 | (code & 0x3F);
    } else if (code < 0xFFFF) {
      uint8[offset++] = 0xE0 | (code >>> 12);
      uint8[offset++] = 0x80 | ((code >>> 6) & 0x3F);
      uint8[offset++] = 0x80 | (code & 0x3F);
    } else {
      uint8[offset++] = 0xF0 | (code >>> 18);
      uint8[offset++] = 0x80 | ((code >>> 12) & 0x3F);
      uint8[offset++] = 0x80 | ((code >>> 6) & 0x3F);
      uint8[offset++] = 0x80 | (code & 0x3F);
    }
  }

  return buffer;
}

console.log(string2ArrayBuffer('我i你'));

Blob作为起点

Blob 至 File

const blob = new Blob(["Hello, world!"], {
    type: "text/plain"
});
const file = new File([blob], "hello.txt", {
    type: "text/plain"
});

console.log(file)

Blob 至 Text

const blob = new Blob(["Hello, world!"], {
    type: "text/plain"
});
const reader = new FileReader();
reader.onload = function () {
    console.log(reader.result);
};
reader.readAsText(blob);

Blob 至 Base64

const blob = new Blob(["Hello, world!"], {
    type: "text/plain"
});
const reader = new FileReader();
reader.onload = function () {
    console.log(reader.result);
};
reader.readAsDataURL(blob);

Blob 至 ArrayBuffer

const blob = new Blob(["Hello, world!"], {
    type: "text/plain"
});
const reader = new FileReader();
reader.onload = function () {
    console.log(reader.result);
};
reader.readAsArrayBuffer(blob);

Blob 至 ObjectURL

const blob = new Blob(["Hello, world!"], {
    type: "text/plain"
});
const objectUrl = URL.createObjectURL(blob);

console.log(objectUrl);

File作为起点

File 至 Text

const file = new File(["Hello, world!"], "hello.txt", {
    type: "text/plain"
});
const reader = new FileReader();
reader.onload = function () {
    console.log(reader.result);
};
reader.readAsText(file);

File 至 Base64

const file = new File(["Hello, world!"], "hello.txt", {
    type: "text/plain"
});
const reader = new FileReader();
reader.onload = function () {
    console.log(reader.result);
};
reader.readAsDataURL(file);

File 至 ArrayBuffer

 const file = new File(["Hello, world!"], "hello.txt", {
     type: "text/plain"
 });
const reader = new FileReader();
reader.onload = function () {
    console.log(reader.result);
};
reader.readAsArrayBuffer(file);

File 至 ObjectURL

const file = new File(["Hello, world!"], "hello.txt", {
    type: "text/plain"
});
const objectUrl = URL.createObjectURL(file);

console.log(objectUrl);

ArrayBuffer 作为起点

ArrayBuffer 至 Base64

const buffer = new ArrayBuffer(8);
const uint8Array = new Uint8Array(buffer);
let binaryString = '';
for (let i = 0; i < uint8Array.length; i++) {
    binaryString += String.fromCharCode(uint8Array[i]);
}
const base64String = btoa(binaryString);
console.log(base64String);

ArrayBuffer 至 Blob

const buffer = new ArrayBuffer(8);
const blob = new Blob([buffer], {
    type: "text/plain"
});

console.log(blob)

ArrayBuffer 至 File

const buffer = new ArrayBuffer(8);
const file = new File([buffer], "file.txt", {
    type: "text/plain",
});

console.log(file)

ArrayBuffer 至 Text

方案一:手动解析ArrayBuffer

const arrayBuffer = new TextEncoder().encode("我i你").buffer

/**
 * 将字节数组转换为字符串(支持 UTF-8 多字节字符)
 * @param {ArrayBuffer} bytes - 字节数组
 * @returns {string} - 转换后的字符串
 */
function bytesToString(bytes) {
    // 将字节数组解码为字符串(支持 UTF-8 多字节字符)
    let result = '';
    const input = new Uint8Array(bytes);

    for (let i = 0; i < input.length; i++) {
        // 将当前字节转为 8 位二进制字符串(不足位数时左侧补零)
        const binary = input[i].toString(2).padStart(8, '0');
        // 通过前导连续的 1(且后跟一个 0)判断是否为多字节字符的起始字节
        // 例如:110xxxxx(2 字节)、1110xxxx(3 字节)、11110xxx(4 字节)
        const leadingOnesMatch = binary.match(/^1+?(?=0)/);

        if (leadingOnesMatch) {
            // 多字节字符的总字节数由前导 1 的个数决定
            const numBytes = leadingOnesMatch[0].length;
            // 起始字节中的有效位(去掉前导位与紧随的 0)
            // 对于 110xxxxx、1110xxxx、11110xxx 等,取后面的 x 位
            let codeBits = binary.slice(7 - numBytes);

            // 依次读取并拼接后续续字节(形如 10xxxxxx)的 6 个有效位
            for (let offset = 1; offset < numBytes; offset++) {
                codeBits += input[i + offset].toString(2).padStart(8, '0').slice(2);
            }

            // 将拼接得到的二进制位转换为码点,再转为字符
            result += String.fromCharCode(parseInt(codeBits, 2));
            // 跳过已处理的续字节
            i += numBytes - 1;
        } else {
            // 单字节(ASCII)字符,直接转换
            result += String.fromCharCode(input[i]);
        }
    }

    // 返回最终拼接的字符串
    return result;
};

console.log(bytesToString(arrayBuffer));

方案二:TextDecoder解析(微信小程序不支持)

const arrayBuffer = new TextEncoder().encode("我i你").buffer

const decoder = new TextDecoder();
const str = decoder.decode(arrayBuffer);

console.log(str);

Base64作为起点

Base64 至 ArrayBuffer

const base64String = "d+eIsW4=";

// 将Base64字符串解码为字符串,这里的编码是“Latin1”
const binaryString = window.atob(base64String);

// 将二进制字符串转换为Uint8Array,这里的编码是“UTF-8”
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
    bytes[i] = binaryString.charCodeAt(i);
}

// 输出ArrayBuffer
console.log(bytes.buffer);

Base64 至 Blob

const base64String = "SGVsbG8gd29ybGQh";

// 将Base64字符串解码为字符串,这里的编码是“Latin1”
const binaryString = window.atob(base64String);

// 将二进制字符串转换为Uint8Array,这里的编码是“UTF-8”
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
    bytes[i] = binaryString.charCodeAt(i);
}

// 创建Blob对象
const blob = new Blob([bytes], { type: "text/plain" });

console.log(bytes)

Base64 至 Text

0-255以内的字符,属于 Latin1 字符集

const base64String = "SGVsbG8gV29ybGQ=";

console.log(window.atob(base64String));

包含中文等...,属于 Unicode 字符集

const text = "dyVFNyU4OCVCMW4=";

let encodedString = decodeURIComponent(window.atob(text));

console.log(encodedString);

encodeURIComponent 要配合 encodeURIComponent 一同使用哦!!!

TextDecoder 配合 charCodeAt
const base64String = "d+eIsW4="; 
const binaryString = window.atob(base64String);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
    bytes[i] = binaryString.charCodeAt(i);
}
console.log(new TextDecoder().decode(bytes));

URL作为起点

URL 至 ArrayBuffer

const audioUrl = 'https://audio-1304256198.cos.ap-guangzhou.myqcloud.com/%E4%BA%B2%E5%88%87%E5%A5%B3%E5%A3%B0.mp3'

const response = await fetch(audioUrl)
const arrayBuffer = await response.arrayBuffer()

后记