为什么在 JavaScript 中 window.btoa 无法处理 "-" 字符?

5

我正在按照下面的代码将字符串转换为BASE64...

var str = "Hello World";
var enc = window.btoa(str);

这将产生SGVsbG8gV29ybGQ=。但是如果我添加这些字符– ”,如下所示的代码,则不会发生转换。这背后的原因是什么?非常感谢。

var str = "Hello – World”";
var enc = window.btoa(str);

嗨!我编辑了这篇文章。谢谢。 - chris_techno25
https://developer.mozilla.org/en-US/docs/Web/API/DOMString/Binary - Mike 'Pomax' Kamermans
4个回答

8

btoa是一种特殊的函数,它要求使用“二进制字符串”这种8位字符格式。它无法处理charcode值超过255的unicode值,例如您使用的em破折号和“花式”引号符号。

您可以将字符串转换为符合单字节打包的新字符串(然后手动重构相关atob的结果),或者您可以先对数据进行URI编码,使其安全:

> var str = `Hello – World`;
> window.btoa(encodeURIComponent(str));
"SGVsbG8lMjAlRTIlODAlOTMlMjBXb3JsZA=="

在解包时,记得再次进行解码:

> var base64= "SGVsbG8lMjAlRTIlODAlOTMlMjBXb3JsZA==";
> decodeURIComponent(window.atob(base64));
"Hello – World"

1
我认为第一个回答错误的人是错了。我会给你点赞。 - chris_techno25
我没有点踩,但 URL 编码不是一个很方便的解决方法。 - Kaiido
2
@Kaiido:这个确切的输入没有base64编码,因为base64在Unicode字符串上没有定义,而是在字节流上定义的。只有特定编码下的字符串表示才具有base64编码,并且编码的选择是任意的。它可以是UTF-8,也可以是URL编码的UTF-8;重要的是端点在编码/解码步骤上达成一致。 - user3840170
1
@user3840170,没错,现在我们需要考虑有多少个端点会期望 URL 编码而不是 UTF-8 编码? - Kaiido
1
当然,但是请注意,“让btoa接受字符串”和“将字符串数据编码为base64”不是相同的事情。 如果您只想传递字符串,那么这样做就可以了。 如果您想要实现从Unicode到Unicode的通用序列化/反序列化,则必须执行我没有提供代码的部分:将您的字符串展平为其字节码等效项。 然后,如果您需要自己解码它(而不是依赖于data-uri解码),则必须编写自己的“扁平字节到正确的Unicode”重建代码,并且在此时,您可能需要一个正确的base64库,而不是 btoa - Mike 'Pomax' Kamermans
显示剩余3条评论

1
问题在于字符超出了Latin1范围。
为此,您可以使用unescape(现已弃用)。

var str = "Hello – World”";
var enc = btoa(unescape(encodeURIComponent(str)));

alert(enc);

并且解码:

var encStr = "SGVsbG8g4oCTIFdvcmxk4oCd";
var dec = decodeURIComponent(escape(window.atob(encStr)))

alert(dec);


根据https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/unescape,完全依赖于`unescape`并不是一个好主意-只需进行编码,然后在`atob`之后解码。 - Mike 'Pomax' Kamermans

0

最可靠的方法是直接处理二进制数据。

为此,您可以将字符串编码为表示其UTF-8版本的ArrayBuffer对象。

然后,FileReader实例将能够轻松地为您提供base64。

var str = "Hello – World”";
var buf = new TextEncoder().encode( str );
var reader = new FileReader();
reader.onload = evt => { console.log( reader.result.split(',')[1] ); };
reader.readAsDataURL( new Blob([buf]) );

由于Blob()构造函数自动将DOMString实例编码为UTF-8,因此我们甚至可以摆脱TextEncoder对象:

var str = "Hello – World”";
var reader = new FileReader();
reader.onload = evt => { console.log( reader.result.split(',')[1] ); };
reader.readAsDataURL( new Blob([str]) );


0

这最终归因于JavaScript类型系统的不足。

JavaScript字符串是由16位代码单元组成的,通常被解释为UTF-16。Base64编码是一种将8位字节流转换为数字串的方法,通过将每三个字节映射到四个数字,每个数字覆盖6位:3×8=4×6。正如我们所看到的,这在很大程度上取决于每个符号的位宽。

在定义btoa函数时,JavaScript没有针对8位字节流的类型,因此API被定义为以普通的16位字符串类型作为输入,并限制每个代码单元应该被限制在[U+0000,U+00FF]范围内;当编码为ISO-8859-1时,这样的字符串将完全复制预期的字节流。

字符是U+2013,而是U+201D;这两个字符都不适合上述范围,因此该函数会拒绝它们。

如果你想将Unicode文本转换为Base64,你需要先选择一个字符编码并将其转换为字节字符串,然后对其进行编码。要求对Unicode字符串本身进行Base64编码是没有意义的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接