执行'btoa'时失败:要编码的字符串包含拉丁1范围之外的字符。

246

根据我的测试,标题中的错误仅在Google Chrome中出现。我正在对一个大型XML文件进行base64编码,以便可以下载:

this.loader.src = "data:application/x-forcedownload;base64,"+
                  btoa("<?xml version=\"1.0\" encoding=\"utf-8\"?>"
                  +"<"+this.gamesave.tagName+">"
                  +this.xml.firstChild.innerHTML
                  +"</"+this.gamesave.tagName+">");

this.loader 是隐藏的 iframe。

这个错误实际上是非常奇怪的,因为通常情况下,Google Chrome 在调用 btoa 时会崩溃。Mozilla Firefox 在这里没有问题,所以这个问题是与浏览器有关的。 我不知道文件中是否有任何奇怪的字符。事实上,我相信没有非 ASCII 字符。

问: 如何找到有问题的字符并替换它们,使得 Chrome 停止报错?

我尝试使用 Downloadify 来启动下载,但它不起作用。它不可靠,并且不抛出错误以允许调试。


这是一个老问题,但我最近遇到了这个问题,并发现这篇 MDN 文章非常有用:https://developer.mozilla.org/en-US/docs/Glossary/Base64#the_unicode_problem - Alex
9个回答

369
如果你有UTF8,可以使用这个(实际上可以与SVG源代码一起使用),像这样:
btoa(unescape(encodeURIComponent(str)))

例子:
 var imgsrc = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(markup)));
 var img = new Image(1, 1); // width, height values are optional params 
 img.src = imgsrc;

如果你需要解码那个base64,可以使用这个:
var str2 = decodeURIComponent(escape(window.atob(b64)));
console.log(str2);

例子:

var str = "äöüÄÖÜçéèñ";
var b64 = window.btoa(unescape(encodeURIComponent(str)))
console.log(b64);

var str2 = decodeURIComponent(escape(window.atob(b64)));
console.log(str2);

注意:如果你想在移动Safari上使其正常工作,可能需要从base64数据中删除所有的空白字符...
function b64_to_utf8( str ) {
    str = str.replace(/\s/g, '');    
    return decodeURIComponent(escape(window.atob( str )));
}

2017 更新

这个问题又困扰了我。
简单来说,atob 并不能真正处理 UTF8 字符串,它只能处理 ASCII 字符串。
而且,我不会使用像 js-base64 这样的臃肿软件。
但是 webtoolkit 确实有一个小巧、优雅且易于维护的实现:

/**
*
*  Base64 encode / decode
*  http://www.webtoolkit.info
*
**/
var Base64 = {

    // private property
    _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="

    // public method for encoding
    , encode: function (input)
    {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;

        input = Base64._utf8_encode(input);

        while (i < input.length)
        {
            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);

            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;

            if (isNaN(chr2))
            {
                enc3 = enc4 = 64;
            }
            else if (isNaN(chr3))
            {
                enc4 = 64;
            }

            output = output +
                this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
                this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
        } // Whend 

        return output;
    } // End Function encode 


    // public method for decoding
    ,decode: function (input)
    {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;

        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
        while (i < input.length)
        {
            enc1 = this._keyStr.indexOf(input.charAt(i++));
            enc2 = this._keyStr.indexOf(input.charAt(i++));
            enc3 = this._keyStr.indexOf(input.charAt(i++));
            enc4 = this._keyStr.indexOf(input.charAt(i++));

            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;

            output = output + String.fromCharCode(chr1);

            if (enc3 != 64)
            {
                output = output + String.fromCharCode(chr2);
            }

            if (enc4 != 64)
            {
                output = output + String.fromCharCode(chr3);
            }

        } // Whend 

        output = Base64._utf8_decode(output);

        return output;
    } // End Function decode 


    // private method for UTF-8 encoding
    ,_utf8_encode: function (string)
    {
        var utftext = "";
        string = string.replace(/\r\n/g, "\n");

        for (var n = 0; n < string.length; n++)
        {
            var c = string.charCodeAt(n);

            if (c < 128)
            {
                utftext += String.fromCharCode(c);
            }
            else if ((c > 127) && (c < 2048))
            {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else
            {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        } // Next n 

        return utftext;
    } // End Function _utf8_encode 

    // private method for UTF-8 decoding
    ,_utf8_decode: function (utftext)
    {
        var string = "";
        var i = 0;
        var c, c1, c2, c3;
        c = c1 = c2 = 0;

        while (i < utftext.length)
        {
            c = utftext.charCodeAt(i);

            if (c < 128)
            {
                string += String.fromCharCode(c);
                i++;
            }
            else if ((c > 191) && (c < 224))
            {
                c2 = utftext.charCodeAt(i + 1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            }
            else
            {
                c2 = utftext.charCodeAt(i + 1);
                c3 = utftext.charCodeAt(i + 2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }

        } // Whend 

        return string;
    } // End Function _utf8_decode 

}

https://www.fileformat.info/info/unicode/utf8.htm

对于任何小于或等于127(十六进制0x7F)的字符,其UTF-8表示形式为一个字节。它只是完整Unicode值的最低7位。这也与ASCII值相同。
对于小于或等于2047(十六进制0x07FF)的字符,UTF-8表示形式跨越两个字节。第一个字节的两个高位设置,第三位清零(即0xC2至0xDF)。第二个字节的最高位设置,第二位清零(即0x80至0xBF)。
对于所有大于或等于2048但小于65535(0xFFFF)的字符,UTF-8表示形式跨越三个字节。
2023年更新: 在我的情况下,主要原因是没有将字符集添加到数据图像URL中。 它应该以...开始。
data:image/svg+xml;charset=utf-8,&lt;svg…

7
你能再解释一下吗?我完全不懂。 - Muhammad Umer
10
在 JavaScript 1.5 中,escapeunescape 已被弃用,应分别使用 encodeURIComponentdecodeURIComponent 来代替。你正在同时使用被弃用和新函数。为什么会这样? 参考:http://www.w3schools.com/jsref/jsref_escape.asp - Leif
3
这之所以能够精确运作,正是因为 escape 和 unescape 存在缺陷(同样的缺陷) ;) - Stefan Steiger
8
有人因使用webpack而来到这里吗? - Avindra Goolcharan
@bplittle 我也不得不反转顺序:y = btoa(escape(x))x = unescape(atob(y))。事实上,我无法想象在给定的顺序下它可能如何工作。 - Tobia
显示剩余19条评论

45

使用库来代替

我们无需重新发明轮子。只需要使用一个库来节省时间和麻烦。

js-base64

https://github.com/dankogai/js-base64很不错,我确认它非常好地支持了 Unicode。

Base64.encode('dankogai');  // ZGFua29nYWk=
Base64.encode('小飼弾');    // 5bCP6aO85by+
Base64.encodeURI('小飼弾'); // 5bCP6aO85by-

Base64.decode('ZGFua29nYWk=');  // dankogai
Base64.decode('5bCP6aO85by+');  // 小飼弾
// note .decodeURI() is unnecessary since it accepts both flavors
Base64.decode('5bCP6aO85by-');  // 小飼弾

4
这是个不错的解决方案,尽管btoa被限制在ASCII字符集似乎有些疏忽(不过atob解码看起来还行)。在其他答案都不能用的情况下,这对我很有效。谢谢! - For the Name
1
这绝对是更简单的解决方案。我需要能够将歌词编码为base64字符串,以便某些文件格式的程序可以读取。这样可以保持字符不变,而没有任何额外的编码可能在稍后这些文件被程序读取时出现。 - FiniteLooper

33

对于我来说,使用btoaunescapeencodeURIComponent不起作用。将所有特殊字符替换为XML/HTML实体,然后转换为base64表示是解决此问题的唯一方法。一些代码:

base64 = btoa(str.replace(/[\u00A0-\u2666]/g, function(c) {
    return '&#' + c.charCodeAt(0) + ';';
}));

1
自从我发布了这个问题,我对专门用于我所做的事情的API有了一些了解。如果要转换的字符串很长,请使用“Blob”对象来处理转换。 “Blob”可以处理任何二进制数据。 - Tomáš Zato
1
不确定IE9是否支持。但我的想法是,如果你正在客户端执行像base64转换这样的操作,那么你可能正在制作现代Web应用程序,迟早需要现代功能。此外,还有一个blob polyfill。 - Tomáš Zato
2
@ItaloBorssatto 你是个传奇! - codeepic
1
@ItaloBorssatto 这是我唯一有效的解决方案。我需要它来获取d3 svg图表,使用XMLSerializer进行序列化,将其传递到btoa()(这就是我使用您的解决方案的地方)以创建一个base-64编码的ASCII字符串,然后将其传递到图像元素中,然后绘制到画布中,最后导出它,以便用户可以在前端下载图像。这是一个相当复杂和hacky的解决方案,但不需要在服务器端渲染图表,当用户想要下载一些图形时非常有用。如果您感兴趣,我可以发送给您一些代码示例。评论太短了,无法包含它们。 - codeepic
1
@ItaloBorssatto <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1060 105" width="1060" height="105"><path class="domain" stroke="none" d="M-6,0.5H0.5V35.5H-6"><line stroke="none" x2="-6" y1="0.5" y2="0.5" fill="none" stroke-width="1px" font-family="sans-serif" font-size="10px"/><text fill="rgb(196, 196, 196)" x="-9" y="0.5" dy="0.32em">VogueEspana - Vogue España</text><rect class="first bar" fill="rgb(25, 244, 71)" x="0" y="8" width="790" height="18" /></g></svg> 我删去了无关的部分。罪魁祸首是Vogue España--> ñ 阻止了图像在浏览器中加载。 - codeepic
显示剩余6条评论

17
我想分享一下我是如何解决这个问题的,以及为什么我认为这是正确的解决方案(前提是您不优化旧浏览器)。
将数据转换为dataURL(data:...
var blob = new Blob(
              // I'm using page innerHTML as data
              // note that you can use the array
              // to concatenate many long strings EFFICIENTLY
              [document.body.innerHTML],
              // Mime type is important for data url
              {type : 'text/html'}
); 
// This FileReader works asynchronously, so it doesn't lag
// the web application
var a = new FileReader();
a.onload = function(e) {
     // Capture result here
     console.log(e.target.result);
};
a.readAsDataURL(blob);

允许用户保存数据

除了显而易见的解决方案——使用你的dataURL作为URL打开新窗口,你还可以做两件事情。

1. 使用fileSaver.js

文件保存器可以创建实际的文件保存对话框,并预定义文件名。它也可以回退到普通的dataURL方法。

2. 使用(实验性的)URL.createObjectURL

这对于重用base64编码的数据非常有用。它为您的dataURL创建一个短网址:

console.log(URL.createObjectURL(blob));
//Prints: blob:http://stackoverflow.com/7c18953f-f5f8-41d2-abf5-e9cbced9bc42

请不要忘记使用包含前缀blob的URL。我再次使用了document.body:

image description

您可以将此短链接用作AJAX目标、<script>源或<a> href位置。但是,您需要负责销毁此URL:

URL.revokeObjectURL('blob:http://stackoverflow.com/7c18953f-f5f8-41d2-abf5-e9cbced9bc42')

谢谢,伙计,你救了我的一天 :) - Sandeep Kumar
1
所有这些想法似乎都是合理的,但在我的尝试中没有一个能够在Chrome上工作...例如,使用我的SO头像(因为那些评论很苛刻):window.location = URL.createObjectURL(new Blob([await fetch('https://www.gravatar.com/avatar/acfb059457d47b1086189cddb2f3857c?s=64&d=identicon&r=PG').then(x => x.text())], {type: 'image/jpg'})),我总是得到一个空白的正方形。 - igorsantos07

10
作为对Stefan Steiger回答的补充:(因为作为评论看起来不太好) 扩展字符串原型:
String.prototype.b64encode = function() { 
    return btoa(unescape(encodeURIComponent(this))); 
};
String.prototype.b64decode = function() { 
    return decodeURIComponent(escape(atob(this))); 
};

使用方法:

var str = "äöüÄÖÜçéèñ";
var encoded = str.b64encode();
console.log( encoded.b64decode() );

注意:

正如评论中所述,不建议使用unescape,因为它可能会在将来被删除:

警告:虽然unescape()并非严格弃用(即“从Web标准中删除”),但它在ECMA-262标准的附录B中定义,其介绍状态如下: …此附录中指定的所有语言特性和行为都具有一个或多个不良特征,在没有遗留用途的情况下将从本规范中删除。

注意:不要使用unescape来解码URI,请改用decodeURIdecodeURIComponent


9
函数看起来很好,但扩展基本原型是不良实践。 - timemachine3030
5
JavaScript是一种不好的做法。再来一个hack,谢谢。 - rob5408
1
@rob5408:虽然我原则上同意你的说法,但你应该更加谨慎:扩展原型会破坏jQuery(另一个使用“再来一个hack”的库)。 - Stefan Steiger
@StefanSteiger 很好知道,感谢您的见解。 - rob5408
unescape将很快被弃用,根据MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/unescape。 - Akansh

5
btoa() 只支持从 String.fromCodePoint(0) 到 String.fromCodePoint(255) 的字符。对于代码点大于 256 的 Base64 字符,您需要在编码/解码之前和之后进行处理。

这时就变得棘手了...

每个可能的符号都排列在 Unicode 表中。Unicode 表分为不同的平面(语言、数学符号等...)。一个平面中的每个符号都有一个唯一的代码点编号。理论上,这个编号可以任意地变大。

计算机以字节(8 位,十六进制 0x00 - 0xff,二进制 00000000 - 11111111,十进制 0 - 255)存储数据。这个范围通常用于保存基本字符(Latin1 范围)。

对于代码点大于 255 的字符,存在不同的编码方式。JavaScript 使用每个符号 16 位(UTF-16),称为 DOMString 的字符串。Unicode 可以处理高达 0x10fffff 的代码点。也就是说,必须存在一种方法来将几个位存储在几个单元格中。

String.fromCodePoint(0x10000).length == 2

UTF-16 使用代理对在两个 16 位单元格中存储 20 位。第一个更高的代理以 110110xxxxxxxxxx 开头,第二个较低的代理以 110111xxxxxxxxxx 开头。Unicode 为此保留了自己的平面:https://unicode-table.com/de/#high-surrogates

为了以字节(Latin1 范围)存储字符,标准化过程使用 UTF-8

很抱歉,但我认为没有其他办法来自己实现这个函数。

function stringToUTF8(str)
{
    let bytes = [];

    for(let character of str)
    {
        let code = character.codePointAt(0);

        if(code <= 127)
        {
            let byte1 = code;

            bytes.push(byte1);
        }
        else if(code <= 2047)
        {
            let byte1 = 0xC0 | (code >> 6);
            let byte2 = 0x80 | (code & 0x3F);

            bytes.push(byte1, byte2);
        }
        else if(code <= 65535)
        {
            let byte1 = 0xE0 | (code >> 12);
            let byte2 = 0x80 | ((code >> 6) & 0x3F);
            let byte3 = 0x80 | (code & 0x3F);

            bytes.push(byte1, byte2, byte3);
        }
        else if(code <= 2097151)
        {
            let byte1 = 0xF0 | (code >> 18);
            let byte2 = 0x80 | ((code >> 12) & 0x3F);
            let byte3 = 0x80 | ((code >> 6) & 0x3F);
            let byte4 = 0x80 | (code & 0x3F);

            bytes.push(byte1, byte2, byte3, byte4);
        }
    }

    return bytes;
}

function utf8ToString(bytes, fallback)
{
    let valid = undefined;
    let codePoint = undefined;
    let codeBlocks = [0, 0, 0, 0];

    let result = "";

    for(let offset = 0; offset < bytes.length; offset++)
    {
        let byte = bytes[offset];

        if((byte & 0x80) == 0x00)
        {
            codeBlocks[0] = byte & 0x7F;

            codePoint = codeBlocks[0];
        }
        else if((byte & 0xE0) == 0xC0)
        {
            codeBlocks[0] = byte & 0x1F;

            byte = bytes[++offset];
            if(offset >= bytes.length || (byte & 0xC0) != 0x80) { valid = false; break; }

            codeBlocks[1] = byte & 0x3F;

            codePoint = (codeBlocks[0] << 6) + codeBlocks[1];
        }
        else if((byte & 0xF0) == 0xE0)
        {
            codeBlocks[0] = byte & 0xF;

            for(let blockIndex = 1; blockIndex <= 2; blockIndex++)
            {
                byte = bytes[++offset];
                if(offset >= bytes.length || (byte & 0xC0) != 0x80) { valid = false; break; }

                codeBlocks[blockIndex] = byte & 0x3F;
            }
            if(valid === false) { break; }

            codePoint = (codeBlocks[0] << 12) + (codeBlocks[1] << 6) + codeBlocks[2];
        }
        else if((byte & 0xF8) == 0xF0)
        {
            codeBlocks[0] = byte & 0x7;

            for(let blockIndex = 1; blockIndex <= 3; blockIndex++)
            {
                byte = bytes[++offset];
                if(offset >= bytes.length || (byte & 0xC0) != 0x80) { valid = false; break; }

                codeBlocks[blockIndex] = byte & 0x3F;
            }
            if(valid === false) { break; }

            codePoint = (codeBlocks[0] << 18) + (codeBlocks[1] << 12) + (codeBlocks[2] << 6) + (codeBlocks[3]);
        }
        else
        {
            valid = false; break;
        }

        result += String.fromCodePoint(codePoint);
    }

    if(valid === false)
    {
        if(!fallback)
        {
            throw new TypeError("Malformed utf-8 encoding.");
        }

        result = "";

        for(let offset = 0; offset != bytes.length; offset++)
        {
            result += String.fromCharCode(bytes[offset] & 0xFF);
        }
    }

    return result;
}

function decodeBase64(text, binary)
{
    if(/[^0-9a-zA-Z\+\/\=]/.test(text)) { throw new TypeError("The string to be decoded contains characters outside of the valid base64 range."); }

    let codePointA = 'A'.codePointAt(0);
    let codePointZ = 'Z'.codePointAt(0);
    let codePointa = 'a'.codePointAt(0);
    let codePointz = 'z'.codePointAt(0);
    let codePointZero = '0'.codePointAt(0);
    let codePointNine = '9'.codePointAt(0);
    let codePointPlus = '+'.codePointAt(0);
    let codePointSlash = '/'.codePointAt(0);

    function getCodeFromKey(key)
    {
        let keyCode = key.codePointAt(0);

        if(keyCode >= codePointA && keyCode <= codePointZ)
        {
            return keyCode - codePointA;
        }
        else if(keyCode >= codePointa && keyCode <= codePointz)
        {
            return keyCode + 26 - codePointa;
        }
        else if(keyCode >= codePointZero && keyCode <= codePointNine)
        {
            return keyCode + 52 - codePointZero;
        }
        else if(keyCode == codePointPlus)
        {
            return 62;
        }
        else if(keyCode == codePointSlash)
        {
            return 63;
        }

        return undefined;
    }

    let codes = Array.from(text).map(character => getCodeFromKey(character));

    let bytesLength = Math.ceil(codes.length / 4) * 3;

    if(codes[codes.length - 2] == undefined) { bytesLength = bytesLength - 2; } else if(codes[codes.length - 1] == undefined) { bytesLength--; }

    let bytes = new Uint8Array(bytesLength);

    for(let offset = 0, index = 0; offset < bytes.length;)
    {
        let code1 = codes[index++];
        let code2 = codes[index++];
        let code3 = codes[index++];
        let code4 = codes[index++];

        let byte1 = (code1 << 2) | (code2 >> 4);
        let byte2 = ((code2 & 0xf) << 4) | (code3 >> 2);
        let byte3 = ((code3 & 0x3) << 6) | code4;

        bytes[offset++] = byte1;
        bytes[offset++] = byte2;
        bytes[offset++] = byte3;
    }

    if(binary) { return bytes; }

    return utf8ToString(bytes, true);
}

function encodeBase64(bytes) {
    if (bytes === undefined || bytes === null) {
        return '';
    }
    if (bytes instanceof Array) {
        bytes = bytes.filter(item => {
            return Number.isFinite(item) && item >= 0 && item <= 255;
        });
    }

    if (
        !(
            bytes instanceof Uint8Array ||
            bytes instanceof Uint8ClampedArray ||
            bytes instanceof Array
        )
    ) {
        if (typeof bytes === 'string') {
            const str = bytes;
            bytes = Array.from(unescape(encodeURIComponent(str))).map(ch =>
                ch.codePointAt(0)
            );
        } else {
            throw new TypeError('bytes must be of type Uint8Array or String.');
        }
    }

    const keys = [
        'A',
        'B',
        'C',
        'D',
        'E',
        'F',
        'G',
        'H',
        'I',
        'J',
        'K',
        'L',
        'M',
        'N',
        'O',
        'P',
        'Q',
        'R',
        'S',
        'T',
        'U',
        'V',
        'W',
        'X',
        'Y',
        'Z',
        'a',
        'b',
        'c',
        'd',
        'e',
        'f',
        'g',
        'h',
        'i',
        'j',
        'k',
        'l',
        'm',
        'n',
        'o',
        'p',
        'q',
        'r',
        's',
        't',
        'u',
        'v',
        'w',
        'x',
        'y',
        'z',
        '0',
        '1',
        '2',
        '3',
        '4',
        '5',
        '6',
        '7',
        '8',
        '9',
        '+',
        '/'
    ];
    const fillKey = '=';

    let byte1;
    let byte2;
    let byte3;
    let sign1 = ' ';
    let sign2 = ' ';
    let sign3 = ' ';
    let sign4 = ' ';

    let result = '';

    for (let index = 0; index < bytes.length; ) {
        let fillUpAt = 0;

        // tslint:disable:no-increment-decrement
        byte1 = bytes[index++];
        byte2 = bytes[index++];
        byte3 = bytes[index++];

        if (byte2 === undefined) {
            byte2 = 0;
            fillUpAt = 2;
        }

        if (byte3 === undefined) {
            byte3 = 0;
            if (!fillUpAt) {
                fillUpAt = 3;
            }
        }

        // tslint:disable:no-bitwise
        sign1 = keys[byte1 >> 2];
        sign2 = keys[((byte1 & 0x3) << 4) + (byte2 >> 4)];
        sign3 = keys[((byte2 & 0xf) << 2) + (byte3 >> 6)];
        sign4 = keys[byte3 & 0x3f];

        if (fillUpAt > 0) {
            if (fillUpAt <= 2) {
                sign3 = fillKey;
            }
            if (fillUpAt <= 3) {
                sign4 = fillKey;
            }
        }

        result += sign1 + sign2 + sign3 + sign4;

        if (fillUpAt) {
            break;
        }
    }

    return result;
}

let base64 = encodeBase64("\u{1F604}"); // unicode code point escapes for smiley
let str = decodeBase64(base64);

console.log("base64", base64);
console.log("str", str);

document.body.innerText = str;

如何使用它:decodeBase64(encodeBase64("\u{1F604}"))

示例:https://jsfiddle.net/qrLadeb8/


非常好!但我不明白你为什么需要使用 stringToUTF8utf8ToString - Benjamin Toueg

5
一个解决方案是将字符串转换为utf-8,这比其他答案中建议的utf-16或URLEncoded版本稍短。它还与其他语言(如Python和PHP)解码字符串的方式更兼容。

编码

function btoa_utf8(value) {
    return btoa(
        String.fromCharCode(
            ...new TextEncoder('utf-8')
                   .encode(value)
        )
    );
}

解码

function atob_utf8(value) {
    const value_latin1 = atob(value);
    return new TextDecoder('utf-8').decode(
        Uint8Array.from(
            { length: value_latin1.length },
            (element, index) => value_latin1.charCodeAt(index)
        )
    )
}

你可以在其中一个中用不同的字符编码替换'utf-8'字符串,如果你喜欢的话。
注意:这取决于TextEncoder类。现在大多数浏览器都支持它,但如果你需要针对旧版本浏览器,请检查是否可用。

4

在不使用 unescape 的情况下,另一种解决浏览器问题的方案:

function ToBinary(str)
{
    let result="";

    str=encodeURIComponent(str);

    for(let i=0;i<str.length;i++)
        if(str[i]=="%")
        {
            result+=String.fromCharCode(parseInt(str.substring(i+1,i+3),16));
            i+=2;
        }
        else
            result+=str[i];

    return result;
}

btoa(ToBinary("тест"));//0YLQtdGB0YI=

看起来运行良好 + 可以使用标准的 PHP base64_decode() 函数进行解码。 - jonas dufek

1
我是一名有用的助手,可以为您翻译文本。

我刚遇到了这个问题。

首先,稍微修改您的代码:

var download = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
                  +"<"+this.gamesave.tagName+">"
                  +this.xml.firstChild.innerHTML
                  +"</"+this.gamesave.tagName+">";

this.loader.src = "data:application/x-forcedownload;base64,"+
                  btoa(download);

然后使用您喜欢的Web检查器,在分配this.loader.src的代码行上设置断点,然后执行此代码:

for (var i = 0; i < download.length; i++) {
  if (download[i].charCodeAt(0) > 255) {
    console.warn('found character ' + download[i].charCodeAt(0) + ' "' + download[i] + '" at position ' + i);
  }
}

根据您的应用程序,替换超出范围的字符可能有效,也可能无效,因为这将修改数据。请参阅MDN关于使用btoa方法的Unicode字符的注意事项。

https://developer.mozilla.org/en-US/docs/Web/API/window.btoa


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