ChatGPT解决这个技术问题 Extra ChatGPT

使用 Javascript 的 atob 解码 base64 不能正确解码 utf-8 字符串

我正在使用 Javascript window.atob() 函数来解码 base64 编码的字符串(特别是来自 GitHub API 的 base64 编码的内容)。问题是我得到了 ASCII 编码的字符(比如 ⢠而不是 )。如何正确处理传入的 base64 编码流,以便将其解码为 utf-8?

您链接的 MDN 页面有一个以短语“用于 Unicode 或 UTF-8 字符串”开头的段落。
你在节点上吗?有比 atob 更好的解决方案

s
se_bastiaan

Unicode 问题

尽管 JavaScript (ECMAScript) 已经成熟,但 Base64、ASCII 和 Unicode 编码的脆弱性已经引起了很多头痛(大部分都在这个问题的历史中)。

考虑以下示例:

const ok = "a";
console.log(ok.codePointAt(0).toString(16)); //   61: occupies < 1 byte

const notOK = "✓"
console.log(notOK.codePointAt(0).toString(16)); // 2713: occupies > 1 byte

console.log(btoa(ok));    // YQ==
console.log(btoa(notOK)); // error

为什么我们会遇到这种情况?

根据设计,Base64 期望二进制数据作为其输入。就 JavaScript 字符串而言,这意味着每个字符仅占用一个字节的字符串。因此,如果您将一个字符串传递给 btoa(),其中包含占用超过一个字节的字符,您将收到错误消息,因为这不被视为二进制数据。

资料来源:MDN(2021 年)

最初的 MDN 文章还介绍了 window.btoa.atob 的损坏性质,它们已在现代 ECMAScript 中得到修复。原始的,现已死亡的 MDN 文章解释说:

“Unicode 问题” 由于 DOMStrings 是 16 位编码的字符串,在大多数浏览器中,如果字符超出 8 位字节(0x00~0xFF)的范围,对 Unicode 字符串调用 window.btoa 将导致字符超出范围异常)。

具有二进制互操作性的解决方案

(继续滚动查看 ASCII base64 解决方案)

资料来源:MDN(2021 年)

MDN 推荐的解决方案是对二进制字符串表示进行实际编码:

编码 UTF8 ⇢ 二进制

// convert a Unicode string to a string in which
// each 16-bit unit occupies only one byte
function toBinary(string) {
  const codeUnits = new Uint16Array(string.length);
  for (let i = 0; i < codeUnits.length; i++) {
    codeUnits[i] = string.charCodeAt(i);
  }
  return btoa(String.fromCharCode(...new Uint8Array(codeUnits.buffer)));
}

// a string that contains characters occupying > 1 byte
let encoded = toBinary("✓ à la mode") // "EycgAOAAIABsAGEAIABtAG8AZABlAA=="

解码二进制⇢ UTF-8

function fromBinary(encoded) {
  const binary = atob(encoded);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < bytes.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return String.fromCharCode(...new Uint16Array(bytes.buffer));
}

// our previous Base64-encoded string
let decoded = fromBinary(encoded) // "✓ à la mode"

这有点失败的地方是,您会注意到编码的字符串 EycgAOAAIABsAGEAIABtAG8AZABlAA== 不再与先前解决方案的字符串 4pyTIMOgIGxhIG1vZGU= 匹配。这是因为它是二进制编码的字符串,而不是 UTF-8 编码的字符串。如果这对您不重要(即,您没有从另一个系统转换以 UTF-8 表示的字符串),那么您就可以开始了。但是,如果您想保留 UTF-8 功能,最好使用下面描述的解决方案。

具有 ASCII base64 互操作性的解决方案

这个问题的整个历史表明,多年来我们不得不通过多少不同的方式来解决损坏的编码系统。尽管最初的 MDN 文章不再存在,但这个解决方案仍然可以说是一个更好的解决方案,并且在解决“Unicode 问题”方面做得很好,同时保留了可以在 base64decode.org 上解码的纯文本 base64 字符串。

有两种可能的方法来解决这个问题:

第一个是转义整个字符串(使用 UTF-8,请参阅 encodeURIComponent),然后对其进行编码;第二种是将 UTF-16 DOMString 转换为 UTF-8 字符数组,然后对其进行编码。

关于先前解决方案的说明:MDN 文章最初建议使用 unescapeescape 来解决 Character Out Of Range 异常问题,但后来它们已被弃用。这里的其他一些答案建议使用 decodeURIComponentencodeURIComponent 解决这个问题,这已被证明是不可靠且不可预测的。此答案的最新更新使用现代 JavaScript 函数来提高速度和现代化代码。

如果您想节省一些时间,您还可以考虑使用库:

js-base64(NPM,非常适合 Node.js)

base64-js

编码 UTF8 ⇢ base64

    function b64EncodeUnicode(str) {
        // first we use encodeURIComponent to get percent-encoded UTF-8,
        // then we convert the percent encodings into raw bytes which
        // can be fed into btoa.
        return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
            function toSolidBytes(match, p1) {
                return String.fromCharCode('0x' + p1);
        }));
    }
    
    b64EncodeUnicode('✓ à la mode'); // "4pyTIMOgIGxhIG1vZGU="
    b64EncodeUnicode('\n'); // "Cg=="

解码 base64 ⇢ UTF8

    function b64DecodeUnicode(str) {
        // Going backwards: from bytestream, to percent-encoding, to original string.
        return decodeURIComponent(atob(str).split('').map(function(c) {
            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        }).join(''));
    }
    
    b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"
    b64DecodeUnicode('Cg=='); // "\n"

(为什么我们需要这样做?('00' + c.charCodeAt(0).toString(16)).slice(-2) 将 0 附加到单个字符串,例如当 c == \n 时,c.charCodeAt(0).toString(16) 返回 a,强制将 a 表示为 0a)。

打字稿支持

这是具有一些额外 TypeScript 兼容性的相同解决方案(通过@MA-Maddin):

// Encoding UTF8 ⇢ base64

function b64EncodeUnicode(str) {
    return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
        return String.fromCharCode(parseInt(p1, 16))
    }))
}

// Decoding base64 ⇢ UTF8

function b64DecodeUnicode(str) {
    return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
    }).join(''))
}

第一个解决方案(已弃用)

这使用了 escapeunescape(现在已弃用,尽管这仍然适用于所有现代浏览器):

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

function b64_to_utf8( str ) {
    return decodeURIComponent(escape(window.atob( str )));
}

// Usage:
utf8_to_b64('✓ à la mode'); // "4pyTIMOgIGxhIG1vZGU="
b64_to_utf8('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"

最后一件事:我在调用 GitHub API 时第一次遇到这个问题。为了让它在 (Mobile) Safari 上正常工作,我实际上必须先从 base64 源中去除所有空白,然后才能解码源。这在 2021 年是否仍然有用,我不知道:

function b64_to_utf8( str ) {
    str = str.replace(/\s/g, '');    
    return decodeURIComponent(escape(window.atob( str )));
}

w3schools.com/jsref/jsref_unescape.asp“在 JavaScript 1.5 版中不推荐使用 unescape() 函数。请改用 decodeURI() 或 decodeURIComponent()。”
你救了我的命,兄弟
更新: MDN 的 The "Unicode Problem" 中的解决方案 #1 已修复,b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU='); 现在可以正确输出“✓ à la mode”
另一种解码方法是 decodeURIComponent(atob('4pyTIMOgIGxhIG1vZGU=').split('').map(x => '%' + x.charCodeAt(0).toString(16)).join('')) 不是性能最高的代码,但它就是这样。
return String.fromCharCode(parseInt(p1, 16)); 具有 TypeScript 兼容性。
T
Tedd Hansen

事情会改变的。 escape/unescape 方法已被弃用。

您可以在对字符串进行 Base64 编码之前对字符串进行 URI 编码。请注意,这不会生成 Base64 编码的 UTF8,而是生成 Base64 编码的 URL 编码数据。双方必须就相同的编码达成一致。

在此处查看工作示例:http://codepen.io/anon/pen/PZgbPW

// encode string
var base64 = window.btoa(encodeURIComponent('€ 你好 æøåÆØÅ'));
// decode string
var str = decodeURIComponent(window.atob(tmp));
// str is now === '€ 你好 æøåÆØÅ'

对于 OP 的问题,js-base64 等第三方库应该可以解决问题。


我想指出的是,您不是在生成输入字符串的 base64,而是他的编码组件。因此,如果您将其发送出去,对方无法将其解码为“base64”并获取原始字符串
你是对的,我已经更新了文本以指出这一点。谢谢。另一种方法似乎是自己实现base64,使用第三方库(例如js-base64)或收到“错误:无法在'Window'上执行'btoa':要编码的字符串包含Latin1范围之外的字符。 "
R
Riccardo Galli

如果将字符串视为字节更适合您,则可以使用以下函数

function u_atob(ascii) {
    return Uint8Array.from(atob(ascii), c => c.charCodeAt(0));
}

function u_btoa(buffer) {
    var binary = [];
    var bytes = new Uint8Array(buffer);
    for (var i = 0, il = bytes.byteLength; i < il; i++) {
        binary.push(String.fromCharCode(bytes[i]));
    }
    return btoa(binary.join(''));
}


// example, it works also with astral plane characters such as '𝒞'
var encodedString = new TextEncoder().encode('✓');
var base64String = u_btoa(encodedString);
console.log('✓' === new TextDecoder().decode(u_atob(base64String)))

谢谢。您的回答对于帮助我完成这项工作至关重要,这让我花了好几天的时间。 +1。 stackoverflow.com/a/51814273/470749
如需更快、更跨浏览器的解决方案(但输出基本相同),请参阅stackoverflow.com/a/53433503/5601591
u_atob 和 u_btoa 使用自 IE10 (2012) 以来每个浏览器中都可用的函数,对我来说看起来很可靠(如果你指的是 TextEncoder,那只是一个例子)
正是我需要的。我的 base64 编码的 UTF-8 字符串来自 Python 脚本 (base64.b64encode),这使得它可以与 UTF-8 字符一起使用,而无需更改 Python 端的任何内容。奇迹般有效!
E
Enrike

适合我的完整文章:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding

我们从 Unicode/UTF-8 编码的部分是

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

function b64_to_utf8( str ) {
   return decodeURIComponent(escape(window.atob( str )));
}

// Usage:
utf8_to_b64('✓ à la mode'); // "4pyTIMOgIGxhIG1vZGU="
b64_to_utf8('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"

这是当今最常用的方法之一。


对我有用,因为我正在尝试解码包含德语变音符号的 Github API 响应。谢谢!!
J
Jackie Han

将 base64 解码为 UTF8 字符串

以下是@brandonscript 当前投票最多的答案

function b64DecodeUnicode(str) {
    // Going backwards: from bytestream, to percent-encoding, to original string.
    return decodeURIComponent(atob(str).split('').map(function(c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
    }).join(''));
}

上面的代码可以工作,但是速度很慢。如果您的输入是一个非常大的 base64 字符串,例如 base64 html 文档有 30,000 个字符。这将需要大量的计算。

这是我的答案,使用内置的 TextDecoder,对于大输入,比上述代码快近 10 倍。

function decodeBase64(base64) {
    const text = atob(base64);
    const length = text.length;
    const bytes = new Uint8Array(length);
    for (let i = 0; i < length; i++) {
        bytes[i] = text.charCodeAt(i);
    }
    const decoder = new TextDecoder(); // default is utf-8
    return decoder.decode(bytes);
}

这实际上是一个非常酷的解决方案。我认为它在过去是行不通的,因为 atob 和 btoa 被破坏了,但现在它们没有了。
M
Manuel G

这是 2018 年更新的解决方案,如 Mozilla Development Resources 中所述

从 UNICODE 编码为 B64

function b64EncodeUnicode(str) {
    // first we use encodeURIComponent to get percent-encoded UTF-8,
    // then we convert the percent encodings into raw bytes which
    // can be fed into btoa.
    return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
        function toSolidBytes(match, p1) {
            return String.fromCharCode('0x' + p1);
    }));
}

b64EncodeUnicode('✓ à la mode'); // "4pyTIMOgIGxhIG1vZGU="
b64EncodeUnicode('\n'); // "Cg=="

从 B64 解码为 UNICODE

function b64DecodeUnicode(str) {
    // Going backwards: from bytestream, to percent-encoding, to original string.
    return decodeURIComponent(atob(str).split('').map(function(c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
    }).join(''));
}

b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU='); // "✓ à la mode"
b64DecodeUnicode('Cg=='); // "\n"

如果我在 Javascript 中使用 b64EncodeUnicode(str) 函数。如何在 PHP 中对其进行解码?你可以将函数 b64DecodeUnicode(str) 转换为 PHP 函数吗?
J
Jack G

我假设人们可能想要一种能够产生广泛使用的 base64 URI 的解决方案。请访问 data:text/plain;charset=utf-8;base64,4pi44pi54pi64pi74pi84pi+4pi/ 观看演示(复制数据 uri,打开一个新标签,将数据 URI 粘贴到地址栏中,然后按 Enter 进入该页面)。尽管这个 URI 是 base64 编码的,但浏览器仍然能够识别高代码点并正确解码它们。缩小后的编码器+解码器为1058字节(+Gzip→589字节)

!function(e){"use strict";function h(b){var a=b.charCodeAt(0);if(55296<=a&&56319>=a)if(b=b.charCodeAt(1),b===b&&56320<=b&&57343>=b){if(a=1024*(a-55296)+b-56320+65536,65535<a)return d(240|a>>>18,128|a>>>12&63,128|a>>>6&63,128|a&63)}else return d(239,191,189);return 127>=a?inputString:2047>=a?d(192|a>>>6,128|a&63):d(224|a>>>12,128|a>>>6&63,128|a&63)}function k(b){var a=b.charCodeAt(0)<<24,f=l(~a),c=0,e=b.length,g="";if(5>f&&e>=f){a=a<<f>>>24+f;for(c=1;c<f;++c)a=a<<6|b.charCodeAt(c)&63;65535>=a?g+=d(a):1114111>=a?(a-=65536,g+=d((a>>10)+55296,(a&1023)+56320)):c=0}for(;c<e;++c)g+="\ufffd";return g}var m=Math.log,n=Math.LN2,l=Math.clz32||function(b){return 31-m(b>>>0)/n|0},d=String.fromCharCode,p=atob,q=btoa;e.btoaUTF8=function(b,a){return q((a?"\u00ef\u00bb\u00bf":"")+b.replace(/[\x80-\uD7ff\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]?/g,h))};e.atobUTF8=function(b,a){a||"\u00ef\u00bb\u00bf"!==b.substring(0,3)||(b=b.substring(3));return p(b).replace(/[\xc0-\xff][\x80-\xbf]*/g,k)}}(""+void 0==typeof global?""+void 0==typeof self?this:self:global)

下面是用于生成它的源代码。

var fromCharCode = String.fromCharCode;
var btoaUTF8 = (function(btoa, replacer){"use strict";
    return function(inputString, BOMit){
        return btoa((BOMit ? "\xEF\xBB\xBF" : "") + inputString.replace(
            /[\x80-\uD7ff\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]?/g, replacer
        ));
    }
})(btoa, function(nonAsciiChars){"use strict";
    // make the UTF string into a binary UTF-8 encoded string
    var point = nonAsciiChars.charCodeAt(0);
    if (point >= 0xD800 && point <= 0xDBFF) {
        var nextcode = nonAsciiChars.charCodeAt(1);
        if (nextcode !== nextcode) // NaN because string is 1 code point long
            return fromCharCode(0xef/*11101111*/, 0xbf/*10111111*/, 0xbd/*10111101*/);
        // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
        if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) {
            point = (point - 0xD800) * 0x400 + nextcode - 0xDC00 + 0x10000;
            if (point > 0xffff)
                return fromCharCode(
                    (0x1e/*0b11110*/<<3) | (point>>>18),
                    (0x2/*0b10*/<<6) | ((point>>>12)&0x3f/*0b00111111*/),
                    (0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111*/),
                    (0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/)
                );
        } else return fromCharCode(0xef, 0xbf, 0xbd);
    }
    if (point <= 0x007f) return nonAsciiChars;
    else if (point <= 0x07ff) {
        return fromCharCode((0x6<<5)|(point>>>6), (0x2<<6)|(point&0x3f));
    } else return fromCharCode(
        (0xe/*0b1110*/<<4) | (point>>>12),
        (0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111*/),
        (0x2/*0b10*/<<6) | (point&0x3f/*0b00111111*/)
    );
});

然后,要解码 base64 数据,HTTP 将数据作为数据 URI 获取或使用以下函数。

var clz32 = Math.clz32 || (function(log, LN2){"use strict";
    return function(x) {return 31 - log(x >>> 0) / LN2 | 0};
})(Math.log, Math.LN2);
var fromCharCode = String.fromCharCode;
var atobUTF8 = (function(atob, replacer){"use strict";
    return function(inputString, keepBOM){
        inputString = atob(inputString);
        if (!keepBOM && inputString.substring(0,3) === "\xEF\xBB\xBF")
            inputString = inputString.substring(3); // eradicate UTF-8 BOM
        // 0xc0 => 0b11000000; 0xff => 0b11111111; 0xc0-0xff => 0b11xxxxxx
        // 0x80 => 0b10000000; 0xbf => 0b10111111; 0x80-0xbf => 0b10xxxxxx
        return inputString.replace(/[\xc0-\xff][\x80-\xbf]*/g, replacer);
    }
})(atob, function(encoded){"use strict";
    var codePoint = encoded.charCodeAt(0) << 24;
    var leadingOnes = clz32(~codePoint);
    var endPos = 0, stringLen = encoded.length;
    var result = "";
    if (leadingOnes < 5 && stringLen >= leadingOnes) {
        codePoint = (codePoint<<leadingOnes)>>>(24+leadingOnes);
        for (endPos = 1; endPos < leadingOnes; ++endPos)
            codePoint = (codePoint<<6) | (encoded.charCodeAt(endPos)&0x3f/*0b00111111*/);
        if (codePoint <= 0xFFFF) { // BMP code point
          result += fromCharCode(codePoint);
        } else if (codePoint <= 0x10FFFF) {
          // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
          codePoint -= 0x10000;
          result += fromCharCode(
            (codePoint >> 10) + 0xD800,  // highSurrogate
            (codePoint & 0x3ff) + 0xDC00 // lowSurrogate
          );
        } else endPos = 0; // to fill it in with INVALIDs
    }
    for (; endPos < stringLen; ++endPos) result += "\ufffd"; // replacement character
    return result;
});

更标准的好处是这个编码器和这个解码器的适用范围更广,因为它们可以用作正确显示的有效 URL。观察。

(function(window){ "use strict"; var sourceEle = document.getElementById("source"); var urlBarEle = document.getElementById("urlBar"); var mainFrameEle = document.getElementById("mainframe"); var gotoButton = document.getElementById("gotoButton"); var parseInt = window.parseInt; var fromCodePoint = String.fromCodePoint; var parse = JSON.parse; function unescape(str){ return str.replace(/\\u[\da-f ]{0,4}|\\x[\da-f]{0,2}|\\u{[^}]*}|\\[bfnrtv"'\\]|\\0[0-7 ]{1,3}|\\\d{1,3}/g, function(match){ try{ if (match.startsWith("\\u{")) return fromCodePoint(parseInt(match.slice(2 ,-1),16)); if (match.startsWith("\\u") || match.startsWith("\\x")) return fromCodePoint(parseInt(match.substring(2),16)); if (match.startsWith("\\0") && match.length > 2) return fromCodePoint(parseInt(match.substring(2),8)); if (/^\\\d/.test(match)) return fromCodePoint(+match.slice(1)); }catch(e){return "\ufffd".repeat(match.length)} return parse('"' + match + '"'); }); } 函数whenChange(){ try{ urlBarEle.value = "data:te xt/plain;charset=UTF-8;base64," + btoaUTF8(unescape(sourceEle.value), true); } 最后{ gotoURL(); } } sourceEle.addEventListener("change",whenChange,{passive:1}); sourceEle.addEventListener("输入",whenChange,{passive:1}); // IFrame 设置: function gotoURL(){mainFrameEle.src = urlBarEle.value} gotoButton.addEventListener("click", gotoURL, {passive: 1}); function urlChanged(){urlBarEle.value = mainFrameEle.src} mainFrameEle.addEventListener("load", urlChanged, {passive: 1}); urlBarEle.addEventListener("keypress", function(evt){ if (evt.key === "enter") evt.preventDefault(), urlChanged(); }, {passive: 1}); var fromCharCode = String.fromCharCode; var btoaUTF8 = (function(btoa, replacer){ "use strict"; return function(inputString, BOMit){ return btoa((BOMit?"\xEF\xBB\xBF":"") + inputString.replace( /[\ x80-\uD7ff\uDC00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]?/g, replacer )); } })(btoa, function(nonAsciiChars){ "use strict"; / / 将 UTF 字符串变成二进制 UTF-8 编码字符串 var point = nonAsciiChars.charCodeAt(0); if (point >= 0xD800 && point <= 0xDBFF) { var nextcode = nonAsciiChars.charCodeAt(1); if (nextcode ! == nextcode) { // NaN 因为字符串是 1 个代码点长 return fromCharCode(0xef/*11101111*/, 0xbf/*10111111*/, 0xbd/*10111101*/); } // https://mathiasbynens.be/ notes/javascript-encoding#surrogate-formulae if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) { point = (point - 0xD800) * 0x400 + nextcode - 0xDC00 + 0x10000; if (point > 0xffff) { return fromCharCode( (0x1e /*0b11110*/<<3) | (point>>>18), (0x2/*0b10*/<<6) | ((point>>>12)&0x3f/*0b00111111*/), (0x2/* 0b10*/<<6) | ((点>>>6)&0x3f/*0b00111111*/), (0x2/*0b10*/<<6 ) | (点&0x3f/*0b00111111*/)); } } else { return fromCharCode(0xef, 0xbf, 0xbd); } } if (point <= 0x007f) { return inputString; } else if (point <= 0x07ff) { return fromCharCode((0x6<<5)|(point>>>6), (0x2<<6)|(point&0x3f/*00111111*/)); } else { return fromCharCode( (0xe/*0b1110*/<<4) | (point>>>12), (0x2/*0b10*/<<6) | ((point>>>6)&0x3f/*0b00111111 */), (0x2/*0b10*/<<6) | (点&0x3f/*0b00111111*/)); } }); setTimeout(whenChange, 0); })(窗户); img:active{opacity:0.8}


上面的代码片段除了非常规范之外,速度也非常快。上面的代码片段在性能上尽可能直接,而不是数据必须在各种形式之间多次转换的间接连续链(例如在 Riccardo Galli 的响应中)。它在编码时只使用一个简单的快速 String.prototype.replace 调用来处理数据,而在解码时只使用一个来解码数据。另一个优点是(特别是对于大字符串),String.prototype.replace 允许浏览器自动处理调整字符串大小的底层内存管理,从而显着提升性能,尤其是在 Chrome 和 Firefox 等对 String.prototype.replace 进行了大量优化的常青浏览器中。最后,锦上添花的是,对于您的拉丁脚本排除用户,不包含任何高于 0x7f 的代码点的字符串的处理速度特别快,因为替换算法不会修改该字符串。

我在 https://github.com/anonyco/BestBase64EncoderDecoder/ 上为此解决方案创建了一个 github 存储库


您能否详细说明“用户创建的方式”与“可由浏览器解释”的含义?使用这个解决方案比 Mozilla 推荐的方案有什么附加值?
@brandonscript Mozilla 与 MDN 不同。 MDN 是用户创建的内容。 MDN 上推荐您的解决方案的页面是用户创建的内容,而不是浏览器供应商创建的内容。
您的解决方案供应商是否已创建?我会的,我建议将功劳归功于起源。如果不是,那么它也是用户创建的,和 MDN 的答案没有什么不同?
@brandonscript 好点。你是对的。我删除了那段文字。另外,查看我添加的演示。
D
Darkves

不推荐使用小的更正、取消转义和转义,因此:

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

function b64_to_utf8( str ) {
     return decodeURIComponent(encodeURIComponent(window.atob(str)));
}


function b64_to_utf8( str ) {
    str = str.replace(/\s/g, '');    
    return decodeURIComponent(encodeURIComponent(window.atob(str)));
}

看起来文档链接现在甚至与此不同,建议使用正则表达式解决方案来管理它。
这不起作用,因为 encodeURIComponentdecodeURIComponent 的倒数,即它只会撤消转换。有关 escapeunescape 发生的情况的详细说明,请参阅 stackoverflow.com/a/31412163/1534459
@canaaerus 我不明白你的评论? escape 和 unescape 已弃用,我只是将它们与 [decode|encode]URIComponent 函数交换 :-) 一切正常。首先阅读问题
@Darkves:使用 encodeURIComponent 的原因是为了正确处理(整个范围的)unicode 字符串。因此,例如 window.btoa(decodeURIComponent(encodeURIComponent('€'))) 给出 Error: String contains an invalid character,因为它与 window.btoa('€') 相同,并且 btoa 不能编码
@Darkves:是的,没错。但是你不能用 EncodeURIComponent 交换 escape 和用 DecodeURIComponent 交换 unescape,因为 Encode 和 escape 方法不做同样的事情。与 decode&unescape 相同。我最初犯了同样的错误,顺便说一句。您应该注意到,如果您获取一个字符串,对其进行 UriEncode,然后对其进行 UriDecode,您将得到与输入相同的字符串。所以这样做是无稽之谈。当您对使用 encodeURIComponent 编码的字符串进行转义时,您不会得到与您输入的字符串相同的字符串,这就是为什么使用 escape/unescape 它可以工作,但不适用于您的。
B
Beejor

下面是一些面向未来的代码,适用于可能缺少 escape/unescape() 的浏览器。请注意,IE 9 和更早版本不支持 atob/btoa(),因此您需要为它们使用自定义 base64 函数。

// Polyfill for escape/unescape
if( !window.unescape ){
    window.unescape = function( s ){
        return s.replace( /%([0-9A-F]{2})/g, function( m, p ) {
            return String.fromCharCode( '0x' + p );
        } );
    };
}
if( !window.escape ){
    window.escape = function( s ){
        var chr, hex, i = 0, l = s.length, out = '';
        for( ; i < l; i ++ ){
            chr = s.charAt( i );
            if( chr.search( /[A-Za-z0-9\@\*\_\+\-\.\/]/ ) > -1 ){
                out += chr; continue; }
            hex = s.charCodeAt( i ).toString( 16 );
            out += '%' + ( hex.length % 2 != 0 ? '0' : '' ) + hex;
        }
        return out;
    };
}

// Base64 encoding of UTF-8 strings
var utf8ToB64 = function( s ){
    return btoa( unescape( encodeURIComponent( s ) ) );
};
var b64ToUtf8 = function( s ){
    return decodeURIComponent( escape( atob( s ) ) );
};

可在此处找到更全面的 UTF-8 编码和解码示例:http://jsfiddle.net/47zwb41o/


D
Diwakar

如果仍然面临问题,请包括上述解决方案,尝试如下,考虑 TS 不支持转义的情况。

blob = new Blob(["\ufeff", csv_content]); // this will make symbols to appears in excel 

对于 csv_content 你可以尝试如下。

function b64DecodeUnicode(str: any) {        
        return decodeURIComponent(atob(str).split('').map((c: any) => {
            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        }).join(''));
    }