在没有Deprecated'Escape'调用的情况下,在JavaScript中转换为Base64

Fes*_*ale 6 javascript base64 encoding

我的名字是非斯都.

我需要通过JavaScript在浏览器中将字符串转换为Base64.这个主题在本网站和Mozilla上都有很好的介绍,建议的解决方案似乎是这样的:

function toBase64(str) {
    return window.btoa(unescape(encodeURIComponent(str)));
}

function fromBase64(str) {
    return decodeURIComponent(escape(window.atob(str)));
}
Run Code Online (Sandbox Code Playgroud)

我做了一些研究,发现escape()并且unescape()已弃用,不应再使用了.考虑到这一点,我尝试删除对已弃用函数的调用,这些函数产生:

function toBase64(str) {
    return window.btoa(encodeURIComponent(str));
}

function fromBase64(str) {
    return decodeURIComponent(window.atob(str));
}
Run Code Online (Sandbox Code Playgroud)

这似乎有效,但它引出了以下问题:

(1)为什么最初提出的解决方案包括调用escape()unescape()?该解决方案是在弃用之前提出的,但可能这些功能在当时增加了某种价值.

(2)是否存在某些边缘情况,我删除这些已弃用的调用会导致我的包装函数失败?

注意:StackOverflow上还有其他更详细和复杂的解决方案来解决string => Base64转换的问题.我确信它们的工作正常,但我的问题与这个特别受欢迎的解决方案有关.

谢谢,

费斯图斯

hum*_*ace 8

TL; DR原则escape()/ unescape()没有必要的,而且你没有被废弃的函数第二个版本是安全的,但它会产生较长的base64编码输出:

  • console.log(decodeURIComponent(atob(btoa(encodeURIComponent("€uro")))))
  • console.log(decodeURIComponent(escape(atob(btoa(unescape(encodeURIComponent("€uro")))))))

两者都创建输出"€uro"但没有escape()/ unescape()具有更长的base64表示的版本

  • btoa(encodeURIComponent("€uro")).length // = 16
  • btoa(unescape(encodeURIComponent("€uro"))).length // = 8

escape()/ unescape()步只能成为必要的,如果对方(例如,在特定的方式进行的不可调节的PHP脚本期待的base64).

长版:

首先,为了更好地了解这两个版本之间的差异toBase64()fromBase64()你上面的建议,让我们看看到btoa()这是在问题的核心.文档说,btoa的命名是助记符,所以

"b"可以被认为代表"二进制",而"a"代表"ASCII".

这有点误导,因为文档急于补充,那

但实际上,由于主要的历史原因,这些函数的输入和输出都是Unicode字符串.

更不完美,btoa()确实只是接受

U + 0000到U + 00FF范围内的字符

只有英文字母数字文本只能用btoa()工作.

在两个版本中都有encodeURIComponent()的目的是帮助处理字符串在U + 0000到U + 00FF范围之外的字符串.一个例子是字符串"uü€"有三个字符

  • a (U + 0061)
  • ä (U + 00E4)
  • (U + 20AC)

这里只有两个第一个字符在范围内.第三个字符,即欧元符号,在外面并window.btoa("€")引发超出范围的错误.为了避免这种错误,需要在U + 0000到U + 00FF的集合内表示"€"的解决方案.这就是window.encodeURIComponent所做的:

window.encodeURIComponent("uü€")
创建以下字符串:
"a%C3%A4%E2%82%AC" 其中一些字符已编码

  • a= a(保持不变)
  • ä= %C3%A4(更改为其utf8表示)
  • = %E2%82%AC(更改为其utf8表示)

(更改为其utf8表示)通过使用字符"%"和字符的utf8表示的每个字节的两位数来工作."%"是U + 0025,因此允许在范围内btoa().window.encodeURIComponent("uü€")然后可以输入结果, btoa()因为它不再具有超出范围的字符:

btoa("a%C3%A4%E2%82%AC") \\ = "YSVDMyVBNCVFMiU4MiVBQw=="

unescape()btoa()和之间使用in 的关键encodeURIComponent()是utf8表示的所有字节都使用3个字符%xx来存储字节0x00到0xFF的所有潜在值.这里unescape() 可以扮演一个可选的角色.这是因为unescape()获取所有这些%xx字节并在允许的U + 0000到0 + 00FF范围内创建一个Unicode字符.

去检查 :

  • btoa(encodeURIComponent("uü€"))).length // = 24
  • btoa(unescape(encodeURIComponent("uü€"))).length // = 8

主要的区别是文本的base64表示的长度减少,代价是通过可选的escape()/ 进行额外的解析unescape(),在主要是ASCII字符集文本的情况下,无论如何都是最小的.

要理解的主要教训btoa()是误导性地命名并且需要Unicode U + 0000到U + 00FF字符,这些字符encodeURIComponent()本身就会生成.已弃用escape()/ unescape()仅具有节省空间的功能,这可能是可取的但不是必需的.Unicode符号> U + 00FF的问题在这里被解决为btoa/atob Unicode问题,它提到了在现代浏览器中改进"所有UTF8 Unicode"到base64编码的方法.


T S*_*T S 7

TL;DR / 简短摘要

不要使用 btoa(encodeURIComponent(str))decodeURIComponent(atob(str))- 那是“废话”。

将字符串转换为 Base64 ”通常意味着“将字符串编码为 UTF-8 并将字节编码为 Base64 ”,这正是btoa(unescape(encodeURIComponent(str)))它的作用。 btoa(encodeURIComponent(str))正在做一些对我能想象的任何情况都没有用的事情,即使它从未像humanityANDpeace s详细答案中解释的那样抛出错误。



“将字符串转换为 Base64”是什么意思?

Base64 是一种二进制到文本的编码,一个字节序列被编码为一个 ASCII 字符序列。1因此不可能将文本直接编码为 Base64。从概念上讲,它始终是一个两步程序:

  1. 将字符串转换为字节(使用一些字符编码
  2. 将字节编码为 Base64

您主要可以使用您想要的任何字符编码(也称为 charset 2Encoding Scheme),它只需要能够表示所有需要的字符,并且两个方向(文本到 Base64 和 Base64 到文本)必须相同。由于有许多不同的字符编码,协议或 API 应定义使用哪一种。如果 API 需要“通过 Base64 编码的字符串”并且没有提及字符编码,那么现在您通常可以假设,需要使用 UTF-8 编码。3

对步骤 1 中的字节进行 Base64 编码非常简单:
a) 取三个输入字节以获得 24 位。
b) 分成四块,每块 6 位,得到 0...63 范围内的四个数字。
c) 通过表格将数字转换为 ASCII 字符并将这些字符添加到输出
d) 转到 a)
有关 Base64 本身的更多详细信息超出了本答案的范围。

有什么作用btoa

现在你可能会想:“这个答案不可能是正确的。它声称,不可能将文本直接编码为 Base64,即使这正是btoa它所做的 - 它需要文本并吐出 Base64。

不。它不接受文本并返回 Base64,它接受一个字符串类型参数并返回 Base64。但是那个字符串参数并不代表文本,它只是一种存储字节序列的奇怪方式。每个字节由一个字符表示,该字符的数字代码点值等于该字节的值。4

HTML 标准中的注释说,“可以认为“b”代表“二进制”,“a”代表“ASCII”。” 与流行的观点相反,我不认为,那btoa是命名不好。它不接受文本,而是接受二进制数据并使用 Base64 生成 ASCII 字符串,因此“ binary to ascii ”的缩写形式是绝对正确的名称。这是参数类型,这是误导。

定义btoaHTML标准简单地说:

[...] 用户代理必须将该参数转换为八位字节序列,其第 n 个八位字节是参数第 n 个字符的代码点的八位表示,然后必须将 base64 算法应用于该八位字节序列,并返回结果。

我不知道也可能永远不会知道,为什么他们没有选择不同的参数类型,例如数字数组。也许在btoa第一次指定时性能没有那么好?

有什么作用unescape(encodeURIComponent(str))

现在您可能会想:“如果将文本转换为 Base64 的第一步是将文本编码为字节,那么如何btoa(unescape(encodeURIComponent(str)))实现?btoa不这样做,但似乎unescapeencodeURIComponent与字符编码没有任何关系?

实际上,encodeURIComponent与字符编码有关。该标准指出:

encodeURIComponent 函数计算一个新的 [...] URI,其中某些代码点的每个实例都被 [...] 表示代码点的 UTF-8 编码的转义序列替换。

所以现在我们有了百分比编码的 UTF-8 字节。要将百分比编码的字节转换为适合 的二进制字符串btoa,可以使用unescape,因为行为描述除其他事项外:

  • 如果 c 是代码单元 0x0025(百分号),则
    • [...如何解码%uXXXX...]
    • 否则如果 k ? 长度 - 3 和 [... 后面跟着两个十六进制数字...] 然后
      • 将 c 设置为代码单元,其值为字符串中索引 k + 1 和 k + 2 处的两个十六进制数字 [...] 表示的整数。

因此,在encodeURIComponent将 UTF-8 字节存储为 后%XXunescape将它们完全按照btoa. 因此,总而言之,btoa(unescape(encodeURIComponent(str)))将文本编码为 UTF-8 字节,然后将其编码为 Base64。

回到最初的问题

如果你忘记了,问题是:

(1) 为什么最初提出的解决方案包括对escape()和 的调用unescape()?该解决方案是在弃用之前提出的,但据推测,这些功能当时增加了某种价值。

(2) 是否存在某些边缘情况,我删除这些已弃用的调用会导致我的包装函数失败?

没有unescape你就不会得到 UTF-8 编码字符串的 Base64 表示。btoa(encodeURIComponent(str))将文本编码为一些奇怪的字节(不是标准化的 Unicode 编码方案,但可以通过将 URI 编码的字符串存储为 ASCII 来获得的字节),然后将其编码为 Base64。所以unescape对于标准一致性来说是必要的——好吧,encodeURIComponentASCII 也是标准化的,但没有人会期待这种奇怪的组合。

如果只有您自己在与 Base64 之间进行转换,那么是的,您可以使用btoa(encodeURIComponent(str))它,并且它永远不会抛出错误,如humanityANDpeace的详细答案中所述(我认为问题(2)已得到充分回答)。

但是,在这种情况下,你可以更好的只是使用的结果encodeURIComponent直接。它已经是纯 ASCII 并且总是btoa(encodeURIComponent(str)). 如果您想要比encodeURIComponent(str)您可以使用的btoa(unescape(encodeURIComponent(str)))尺寸更小(如果输入字符串包含更多非 ASCII 字符,则更小)。

如果你转换为 Base64,因为其他方、API 或协议需要 Base64,那么你根本无法使用btoa(encodeURIComponent(str)),因为没有人理解结果。

哦,并btoa(unescape(encodeURIComponent(str)))不能真正“在弃用之前提出unescape:
unescape已从版本 3 中的标准中删除,与添加encodeURIComponent. unescape仍在文档中进行了解释,但移至附件 B.2,其介绍指出,它“建议统一语义[...],但并未将属性或其语义作为本标准的一部分。” 但由于浏览器必须向后兼容,它可能不会很快被删除


自己试试:

function run(){
    let Base64Function=new Function("str", $("#algorithm").val());
    let base64=Base64Function($("#input").val());
    $("#Base64Text").text("Output: "+base64);
    let charset=$('#charset').val();
    let uri="data:text/plain"
           +(charset?";charset="+charset:'')
           +($("#interpret").prop('checked')?";base64":'')
           +","+base64;
    $("#dataURI").text(uri);
    $("#dataURI").attr('href', uri);
    $("#Base64iframe").attr('src',uri);
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<label for="input">Text to encode:</label>
<input type="text" id="input" value="abc€"/><br />

<label for="algorithm">Encode function:</label>
<input type="text" id="algorithm" size="50"/><br />

<button type="button" onclick="run();">Run</button>
Defaults:
<button type="button" onclick='
    $("#algorithm").val("return btoa(unescape(encodeURIComponent(str)))");
    $("#charset").val("UTF-8");
    $("#interpret").prop("checked",true);
'>UTF-8 Base64</button>
<button type="button" onclick='
    $("#algorithm").val("return btoa(encodeURIComponent(str))");
    $("#charset").val(""); //I don't know - it's not UTF-8
    $("#interpret").prop("checked",true);
'>wrong</button>
<button type="button" onclick='
    $("#algorithm").val("return encodeURIComponent(str)");
    $("#charset").val("UTF-8");
    $("#interpret").prop("checked",false);
'>without btoa (not Base64)</button>
<br />

<div id="Base64Text">Output:</div>

<label for="charset">Interpret as this character encoding:</label>
<input type="text" id="charset" /><br />

<label for="interpret">Interpret as Base64:</label>
<input type="checkbox" id="interpret" /><br />

<div><a id="dataURI"></a></div>
<iframe id="Base64iframe"></iframe>
Run Code Online (Sandbox Code Playgroud)

此代码段通过创建 dataURI 来测试 Base64 结果,但该概念也适用于 Base64 的其他应用程序。


笔记:

在一些引文中,我使用[]省略或缩短我认为不重要的事情。
[... some text ...]显然不是源的一部分。

脚注:

1该标准说 Base64 “旨在表示任意八位字节序列”(八位字节表示由八位组成的字节)

2字符集是不完全一样的字符编码。然而,一个编码的字符集总是可以被认为是隐含地定义了一个字符编码,因此“字符集”和“字符编码”经常被用作同义词。也许它曾经是一样的?有时,术语字符集明确用作字符编码而不是字符集的简称。

3至少 UTF-8 对网站来说非常重要。另请参阅UTF-8 无处不在

4这实际上是 ISO_8859-1 编码,但我不会这么认为。最好想想bytes[i]==str.charCodeAt(i)