JavaScript 中文(或非 ASCII 字符)与 base64 互转

由于浏览器和 Nodejs 中的接口等不一致,所以需要分类讨论

浏览器

此方案基于 utf-8 编码实现编/解码。经过个人探索,除了 utf-8 外,JavaScript 字符串仅可以使用 utf-16 来编解码,原理类似,仅在取二进制值时不同,不在此赘述,留作思考题。

实现

如果仅仅只是快速采用轮子,可以直接 cv 下面的代码。

javascript 版:

function encode64(text) {
  return btoa(String.fromCharCode(...new TextEncoder().encode(text)))
}

function decode64(text) {
  return new TextDecoder().decode(Uint8Array.from(atob(text), (c) => c.charCodeAt(0)))
}

typescript 版:

function encode64(text: string): string {
  return btoa(String.fromCharCode(...new TextEncoder().encode(text)))
}

function decode64(text: string): string {
  return new TextDecoder().decode(Uint8Array.from(atob(text), (c) => c.charCodeAt(0)))
}

原理讲解

浏览器中用于将字符串和 base64 互转的 api 为 atobbtoa ,但是这两个 API 只支持 Latin-1 字符集。如果需要对中文进行编码,btoa 则会出现如下错误:

Uncaught DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.

atob 则无法解码出正确的字符串

// '5Lit5paH' 为 '中文' 的 utf-8 的 base64 编码
console.log(atob('5Lit5paH'))
// 输出: '中æ\x96\x87'

mdn 中的解释如下

根据设计,Base64 仅将二进制数据作为其输入。而在 JavaScript 字符串中,这意味着每个字符只能使用一个字节表示。所以,如果你将一个字符串传递给 btoa(),而其中包含了需要使用超过一个字节才能表示的字符,你就会得到一个错误,因为这个字符串不能被看作是二进制数据

所以对于中文编码成 base64,我们的思路就很简单:取字符串的二进制(utf-8 也好 utf-16 也好),取每一字节当做一个字符的码点,重新生成一个字符串,再拿这个字符串去调用 btoa,就可以成功转出来 base64 了。

那么第一步就是拿到这个字符串的 utf-8 的二进制流。虽然 JavaScript 默认使用 utf-16,但是其提供了 TextEncoder.encode()TextDecoder.decode() 用于字符串和 utf-8 字节流转换。接下来以中文这个字符串为例,分编码解码进行讲解。

编码

对于编码,我们可以通过下面的代码拿到一个字符串的 utf-8 字节流

new TextEncoder().encode('中文')
// 此字节流的值为 Uint8Array: [228, 184, 173, 230, 150, 135]

现在我们就拿到了中文的二进制,为 228, 184, 173, 230, 150, 135,一共6位。

接下来就可以拿这6个字节来构造6个字符,形成一个字符串。这些二进制的值又被称为一个字符的码点,所以我们可以使用 String.fromCharCode() 这个静态方法将这 6 个值转换成 6 个字符

静态 String.fromCharCode() 方法返回由指定的 UTF-16 代码单元序列创建的字符串。

String.fromCharCode(num1[, ...[, numN]])

—— mdn

String.fromCharCode() 的函数签名和 Uint8Array 的数组特性可知,我们可以直接使用下面的代码将得到的二进制字节流转换成字符串

String.fromCharCode(...new TextEncoder().encode('中文'))
// 值: '中æ\x96\x87'

现在我们将一个 utf-16 的字符串成功转成了 utf-8 字节流对应的字符串,现在我们就可以使用 btoa() 将这个字符串转换成 base64 编码了。

btoa(String.fromCharCode(...new TextEncoder().encode('中文')))
// 值: '5Lit5paH'
解码

对于解码,首先我们使用 atob() 将上面得到的 base64 编码转换成字符串。

atob('5Lit5paH')
// 值: '中æ\x96\x87'

接下来我们需要将这个字符串转换成一个 Uint8Array 二进制字节流,这里我们可以使用 Uint8Array.from() 这个 api 来将字符串转换成 Uint8Array 字节流。

TypedArray.from() 方法 从一个类数组或者可迭代对象中创建一个新类型数组。这个方法和 Array.from() 类似。

TypedArray.from(arrayLike, mapFn)
TypedArray.from(arrayLike, mapFn, thisArg)

—— mdn

但是如果我们如下代码直接传递给 Uint8Array 的话,会发现这个二进制字节流的每一位都是 0

Uint8Array.from(atob('5Lit5paH'))
// 值: Uint8Array: [0, 0, 0, 0, 0, 0]

此处明显为 Uint8Array.from 没有正确取到该字符串每一位的码点(即使是使用 Uint16Array 也不行),但是观察他的函数签名,我们可以发现有 mapFn 这个参数,所以我们可以通过这个参数取到每一个字符的码点。

对于取字符的码点,有 String.prototype.charCodeAt() 这个 api。所以转换成二进制字节流的代码如下:

Uint8Array.from(atob('5Lit5paH'), (c) => c.charCodeAt(0))
// 值: Uint8Array: [228, 184, 173, 230, 150, 135]

现在我们就可以将其传递给 TextDecoder.decode() 转换成字符串了!

new TextDecoder().decode(Uint8Array.from(atob(text), (c) => c.charCodeAt(0)))

思考题

上面的实现为通过 utf-8 编码的二进制流与 base64 互转的实现,那么如果通过 JavaScript 默认的 utf-16 又该怎么实现呢?

提示:可以参考 mdn 中的此部分进行实现,并优化到一行代码。

Nodejs

Nodejs 中由于有原生的 api 用于转换 base64,在 vscode 中如果把鼠标放到 atob() 或者 btoa() 上,可以得到如下说明

This function is only provided for compatibility with legacy web platform APIs and should never be used in new code, because they use strings to represent binary data and predate the introduction of typed arrays in JavaScript. For code running using Node.js APIs, converting between base64-encoded strings and binary data should be performed using Buffer.from(str, 'base64') andbuf.toString('base64').

据此实现就很简单了。

实现

JavaScript 版:

function encode64(text) {
  return Buffer.from(text).toString('base64')
}

function decode64(text) {
  return Buffer.from(text, 'base64').toString()
}

typescript 版:

function encode64(text: string): string {
  return Buffer.from(text).toString('base64')
}

function decode64(text: string): string {
  return Buffer.from(text, 'base64').toString()
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在微信小程序中,字符串含中文base64可以使用以下代码实现: ```javascript // 将字符成UTF-8编码的ArrayBuffer function str2ab(str) { let buf = new ArrayBuffer(str.length * 2); // 每个字符占用2个字节 let bufView = new Uint16Array(buf); for (let i = 0, strLen = str.length; i < strLen; i++) { bufView[i] = str.charCodeAt(i); } return buf; } // 将ArrayBufferbase64字符串 function ab2base64(arrayBuffer) { let base64 = wx.arrayBufferToBase64(arrayBuffer); return base64; } // 示例 let str = "你好,世界!"; let arrayBuffer = str2ab(str); let base64 = ab2base64(arrayBuffer); console.log(base64); // 5L2g5aW977yM5LiW55WM77yB ``` 要注意,在字符串含有中文等非ASCII字符时,必须将字符成UTF-8编码的ArrayBuffer,否则会出现乱码。 而base64回显中文,则需要将base64字符回原始字符串,方法如下: ```javascript // 将base64字符成ArrayBuffer function base642ab(base64) { let arrayBuffer = wx.base64ToArrayBuffer(base64); return arrayBuffer; } // 将ArrayBuffer字符串 function ab2str(arrayBuffer) { let decoder = new TextDecoder("utf-8"); let str = decoder.decode(arrayBuffer); return str; } // 示例 let base64 = "5L2g5aW977yM5LiW55WM77yB"; let arrayBuffer = base642ab(base64); let str = ab2str(arrayBuffer); console.log(str); // 你好,世界! ``` 同样需要注意,在base64字符串含有中文等非ASCII字符时,必须将base64字符成UTF-8编码的ArrayBuffer。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值