Sử dụng atob của Javascript để giải mã base64 không giải mã đúng cách chuỗi utf-8


106

Tôi đang sử dụng window.atob()hàm Javascript để giải mã chuỗi được mã hóa base64 (cụ thể là nội dung được mã hóa base64 từ API GitHub). Vấn đề là tôi đang nhận lại các ký tự được mã hóa ASCII (như â¢thay vì ). Làm cách nào để xử lý đúng cách luồng được mã hóa base64 đến để nó được giải mã dưới dạng utf-8?


3
Trang MDN bạn đã liên kết có một đoạn bắt đầu bằng cụm từ "Để sử dụng với chuỗi Unicode hoặc UTF-8".
Pointy

1
Bạn đang ở trên nút? Có nhiều giải pháp tốt hơnatob
Bergi

Câu trả lời:


268

Có một bài viết tuyệt vời trên tài liệu MDN của Mozilla mô tả chính xác vấn đề này:

"Vấn đề Unicode" Vì DOMStrings là các chuỗi được mã hóa 16 bit, trong hầu hết các trình duyệt gọi window.btoachuỗi Unicode sẽ gây ra Character Out Of Range exceptionnếu một ký tự vượt quá phạm vi của byte 8 bit (0x00 ~ 0xFF). Có hai phương pháp khả thi để giải quyết vấn đề này:

  • đầu tiên là thoát toàn bộ chuỗi (với UTF-8, xem encodeURIComponent) và sau đó mã hóa nó;
  • cách thứ hai là chuyển đổi UTF-16 DOMStringthành mảng ký tự UTF-8 và sau đó mã hóa nó.

Lưu ý về các giải pháp trước đây: bài viết MDN ban đầu đề xuất sử dụng unescapeescapeđể giải quyết Character Out Of Rangevấn đề ngoại lệ, nhưng chúng đã không được chấp nhận nữa. Một số câu trả lời khác ở đây đã gợi ý giải quyết vấn đề này với decodeURIComponentencodeURIComponent, điều này đã được chứng minh là không đáng tin cậy và không thể đoán trước được. Bản cập nhật gần đây nhất cho câu trả lời này sử dụng các hàm JavaScript hiện đại để cải thiện tốc độ và hiện đại hóa mã.

Nếu bạn đang cố gắng tiết kiệm thời gian cho mình, bạn cũng có thể cân nhắc sử dụng thư viện:

Mã hóa 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=="

Giải mã 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"

Giải pháp trước năm 2018 (chức năng và mặc dù có khả năng hỗ trợ tốt hơn cho các trình duyệt cũ hơn, nhưng không cập nhật)

Đây là khuyến nghị hiện tại, trực tiếp từ MDN, với một số khả năng tương thích TypeScript bổ sung qua @ 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))
    }))
}

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

// 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(''))
}

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

Giải pháp ban đầu (không được dùng nữa)

Điều này đã được sử dụng escapeunescape(hiện không được dùng nữa, mặc dù điều này vẫn hoạt động trong tất cả các trình duyệt hiện đại):

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"

Và một điều cuối cùng: Lần đầu tiên tôi gặp sự cố này khi gọi API GitHub. Để điều này hoạt động bình thường trên Safari (Di động), tôi thực sự phải loại bỏ tất cả khoảng trắng khỏi nguồn base64 trước khi tôi có thể giải mã nguồn. Liệu điều này có còn phù hợp trong năm 2017 hay không, tôi không biết:

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

1
w3schools.com/jsref/jsref_unescape.asp "Hàm unescape () không được dùng nữa trong JavaScript phiên bản 1.5. Thay vào đó, hãy sử dụng decodeURI () hoặc decodeURIComponent ()."
Tedd Hansen

1
Bạn đã cứu những ngày của tôi, người
anh em

2
Cập nhật: Giải pháp # 1 trong MDN là Các "Unicode Vấn đề" đã được cố định, b64DecodeUnicode('4pyTIMOgIGxhIG1vZGU=');bây giờ chính xác đầu ra "✓ à la mode"
weeix

2
Một cách khác để giải mã sẽ decodeURIComponent(atob('4pyTIMOgIGxhIG1vZGU=').split('').map(x => '%' + x.charCodeAt(0).toString(16)).join('')) không phải là mã hiệu quả nhất, nhưng nó là những gì nó là.
daniel.gindi

2
return String.fromCharCode(parseInt(p1, 16));để có khả năng tương thích với TypeScript.
Martin Schneider

20

Nhiều thứ thay đổi. Các phương thức thoát / unescape không được dùng nữa.

Bạn có thể mã hóa URI chuỗi trước khi mã hóa Base64. Lưu ý rằng điều này không tạo ra UTF8 được mã hóa Base64 mà là dữ liệu URL được mã hóa Base64. Cả hai bên phải đồng ý về cùng một bảng mã.

Xem ví dụ làm việc tại đây: http://codepen.io/anon/pen/PZgbPW

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

Đối với vấn đề của OP, một thư viện của bên thứ ba như js-base64 sẽ giải quyết được vấn đề.


1
Tôi muốn chỉ ra rằng bạn không tạo ra base64 của chuỗi đầu vào, mà là thành phần được mã hóa của nó. Vì vậy, nếu bạn gửi nó đi bên kia không thể giải mã nó như là "base64" và nhận được chuỗi gốc
Riccardo Galli

3
Bạn đã đúng, tôi đã cập nhật văn bản để chỉ ra điều đó. Cảm ơn. Giải pháp thay thế dường như đang tự triển khai base64, sử dụng thư viện của bên thứ ba (chẳng hạn như js-base64) hoặc nhận được "Lỗi: Không thực thi được 'btoa' trên 'Cửa sổ': Chuỗi được mã hóa chứa các ký tự nằm ngoài phạm vi Latin1. "
Tedd Hansen

14

Nếu việc coi các chuỗi là byte là việc của bạn, bạn có thể sử dụng các hàm sau

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
Cảm ơn. Câu trả lời của bạn rất quan trọng trong việc giúp tôi làm được điều này, điều này khiến tôi mất nhiều giờ trong nhiều ngày. +1. stackoverflow.com/a/51814273/470749
Ryan

Để có giải pháp đa trình duyệt nhanh hơn và nhiều hơn (nhưng về cơ bản cùng một đầu ra), vui lòng xem stackoverflow.com/a/53433503/5601591
Jack Giffin

u_atob và u_btoa sử dụng các chức năng có sẵn trong mọi trình duyệt kể từ IE10 (2012), có vẻ ổn đối với tôi (nếu bạn tham khảo TextEncoder, đó chỉ là một ví dụ)
Riccardo Galli

4

Đây là giải pháp cập nhật năm 2018 như được mô tả trong Tài nguyên phát triển Mozilla

ĐỂ THƯỞNG THỨC TỪ UNICODE ĐẾN 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=="

ĐỂ GỬI TỪ B64 ĐẾN 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"

3

Tôi giả định rằng người ta có thể muốn một giải pháp tạo ra một URI base64 có thể sử dụng rộng rãi. Vui lòng truy cập data:text/plain;charset=utf-8;base64,4pi44pi54pi64pi74pi84pi+4pi/để xem phần trình diễn (sao chép uri dữ liệu, mở tab mới, dán URI dữ liệu vào thanh địa chỉ, sau đó nhấn enter để truy cập trang). Mặc dù thực tế là URI này được mã hóa base64, trình duyệt vẫn có thể nhận ra các điểm mã cao và giải mã chúng đúng cách. Bộ mã hóa + giải mã thu nhỏ là 1058 byte (+ Gzip → 589 byte)

!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)

Dưới đây là mã nguồn được sử dụng để tạo ra nó.

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*/)
    );
});

Sau đó, để giải mã dữ liệu base64, HTTP lấy dữ liệu dưới dạng URI dữ liệu hoặc sử dụng hàm bên dưới.

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;
});

Ưu điểm của tiêu chuẩn hơn là bộ mã hóa này và bộ giải mã này được áp dụng rộng rãi hơn vì chúng có thể được sử dụng như một URL hợp lệ hiển thị chính xác. Quan sát.

(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 + '"');
        });
    }
    
    function whenChange(){
      try{ urlBarEle.value = "data:text/plain;charset=UTF-8;base64," + btoaUTF8(unescape(sourceEle.value), true);
      } finally{ gotoURL(); }
    }
    sourceEle.addEventListener("change",whenChange,{passive:1});
    sourceEle.addEventListener("input",whenChange,{passive:1});
    
    // IFrame Setup:
    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";
    	// 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 1code 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 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) | (point&0x3f/*0b00111111*/)
    		);
    	}
    });
    setTimeout(whenChange, 0);
})(window);
img:active{opacity:0.8}
<center>
<textarea id="source" style="width:66.7vw">Hello \u1234 W\186\0256ld!
Enter text into the top box. Then the URL will update automatically.
</textarea><br />
<div style="width:66.7vw;display:inline-block;height:calc(25vw + 1em + 6px);border:2px solid;text-align:left;line-height:1em">
<input id="urlBar" style="width:calc(100% - 1em - 13px)" /><img id="gotoButton" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABsAAAAeCAMAAADqx5XUAAAAclBMVEX///9NczZ8e32ko6fDxsU/fBoSQgdFtwA5pAHVxt+7vLzq5ex23y4SXABLiiTm0+/c2N6DhoQ6WSxSyweVlZVvdG/Uz9aF5kYlbwElkwAggACxs7Jl3hX07/cQbQCar5SU9lRntEWGum+C9zIDHwCGnH5IvZAOAAABmUlEQVQoz7WS25acIBBFkRLkIgKKtOCttbv//xdDmTGZzHv2S63ltuBQQP4rdRiRUP8UK4wh6nVddQwj/NtDQTvac8577zTQb72zj65/876qqt7wykU6/1U6vFEgjE1mt/5LRqrpu7oVsn0sjZejMfxR3W/yLikqAFcUx93YxLmZGOtElmEu6Ufd9xV3ZDTGcEvGLbMk0mHHlUSvS5svCwS+hVL8loQQyfpI1Ay8RF/xlNxcsTchGjGDIuBG3Ik7TMyNxn8m0TSnBAK6Z8UZfp3IbAonmJvmsEACum6aNv7B0CnvpezDcNhw9XWsuAr7qnRg6dABmeM4dTgn/DZdXWs3LMspZ1KDMt1kcPJ6S1icWNp2qaEmjq6myx7jbQK3VKItLJaW5FR+cuYlRhYNKzGa9vF4vM5roLW3OSVjkmiGJrPhUq301/16pVKZRGFYWjTP50spTxBN5Z4EKnSonruk+n4tUokv1aJSEl/MLZU90S3L6/U6o0J142iQVp3HcZxKSo8LfkNRCtJaKYFSRX7iaoAAUDty8wvWYR6HJEepdwAAAABJRU5ErkJggg==" style="width:calc(1em + 4px);line-height:1em;vertical-align:-40%;cursor:pointer" />
<iframe id="mainframe" style="width:66.7vw;height:25vw" frameBorder="0"></iframe>
</div>
</center>

Ngoài việc rất chuẩn, các đoạn mã trên cũng rất nhanh. Thay vì một chuỗi kế tiếp gián tiếp trong đó dữ liệu phải được chuyển đổi nhiều lần giữa các dạng khác nhau (chẳng hạn như trong phản hồi của Riccardo Galli), đoạn mã trên càng trực tiếp càng tốt. Nó chỉ sử dụng một String.prototype.replacelệnh gọi nhanh đơn giản để xử lý dữ liệu khi mã hóa và chỉ một lệnh gọi để giải mã dữ liệu khi giải mã. Một điểm cộng khác là (đặc biệt đối với các chuỗi lớn), String.prototype.replacecho phép trình duyệt tự động xử lý việc quản lý bộ nhớ cơ bản của việc thay đổi kích thước chuỗi, dẫn đến tăng hiệu suất đáng kể, đặc biệt là trong các trình duyệt thường xanh như Chrome và Firefox tối ưu hóa nhiềuString.prototype.replace. Cuối cùng, sự đóng băng trên bánh là đối với bạn là người dùng tập lệnh latin, không chứa bất kỳ điểm mã nào trên 0x7f được xử lý nhanh hơn vì chuỗi vẫn không bị sửa đổi bởi thuật toán thay thế.

Tôi đã tạo kho lưu trữ github cho giải pháp này tại https://github.com/anonyco/BestBase64EncoderDecoder/


Bạn có thể giải thích rõ hơn về ý nghĩa của "cách do người dùng tạo" so với "có thể hiểu được bởi trình duyệt" không? Giá trị gia tăng của việc sử dụng giải pháp này hơn là những gì Mozilla đề xuất?
brandonscript

@brandonscript Mozilla khác với MDN. MDN là nội dung do người dùng tạo. Trang trên MDN đề xuất giải pháp của bạn là nội dung do người dùng tạo, không phải nội dung do nhà cung cấp trình duyệt tạo.
Jack Giffin

Nhà cung cấp giải pháp của bạn có được tạo không? Tôi muốn vậy, tôi khuyên bạn nên ghi công vào nguồn gốc. Nếu không, thì nó cũng do người dùng tạo, và không khác gì câu trả lời của MDN?
brandonscript

@brandonscript Điểm tốt. Bạn nói đúng. Tôi đã xóa đoạn văn bản đó. Ngoài ra, hãy xem bản demo tôi đã thêm.
Jack Giffin

3

Toàn bộ bài viết phù hợp với tôi: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding

Phần mà chúng tôi mã hóa từ Unicode / UTF-8 là

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"

Đây là một trong những phương pháp được sử dụng nhiều nhất hiện nay.


Đó là liên kết giống như câu trả lời được chấp nhận.
brandonscript

0

Chỉnh sửa nhỏ, unescape và thoát không được dùng nữa, vì vậy:

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)));
}

2
Có vẻ như liên kết doc thậm chí còn khác với liên kết này, đề xuất một giải pháp regex để quản lý nó.
brandonscript

2
Điều này sẽ không hoạt động, vì encodeURIComponentlà nghịch đảo của decodeURIComponent, tức là nó sẽ chỉ hoàn tác chuyển đổi. Xem stackoverflow.com/a/31412163/1534459 để có lời giải thích tuyệt vời về những gì đang xảy ra với escapeunescape.
bodo

1
@canaaerus Tôi không hiểu nhận xét của bạn? Escape và unescape không được dùng nữa, tôi chỉ cần hoán đổi chúng bằng hàm [decode | encode] URIComponent :-) Mọi thứ đều hoạt động tốt. Đọc câu hỏi trước
Darkves

1
@Darkves: Lý do tại sao encodeURIComponentđược sử dụng, là để xử lý chính xác (toàn bộ phạm vi) các chuỗi unicode. Vì vậy, ví dụ: window.btoa(decodeURIComponent(encodeURIComponent('€')))đưa ra Error: String contains an invalid characterbởi vì nó giống như window.btoa('€')btoakhông thể mã hóa .
bodo

2
@Darkves: Vâng, đúng vậy. Nhưng bạn không thể hoán đổi thoát với EncodeURIComponent và unescape với DecodeURIComponent, vì Mã hóa và các phương thức thoát không làm cùng một điều. Tương tự với decode & unescape. Tôi ban đầu cũng mắc lỗi tương tự, btw. Bạn nên lưu ý rằng nếu bạn lấy một chuỗi, UriEncode nó, sau đó UriDecode nó, bạn sẽ nhận lại được chính chuỗi mà bạn đã nhập. Vì vậy, làm điều đó sẽ là vô nghĩa. Khi bạn hủy bỏ một chuỗi được mã hóa bằng encodeURIComponent, bạn sẽ không lấy lại được chính chuỗi mà bạn đã nhập, vì vậy đó là lý do tại sao với Escape / unescape, nó hoạt động, nhưng không hoạt động với của bạn.
Stefan Steiger

0

Dưới đây là một số mã bảo vệ trong tương lai cho các trình duyệt có thể thiếu escape/unescape(). Lưu ý rằng IE 9 trở lên không hỗ trợ atob/btoa(), vì vậy bạn cần sử dụng các hàm base64 tùy chỉnh cho chúng.

// 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 ) ) );
};

Có thể tìm thấy một ví dụ toàn diện hơn về mã hóa và giải mã UTF-8 tại đây: http://jsfiddle.net/47zwb41o/


-1

kể cả giải pháp trên nếu vẫn gặp sự cố hãy thử như dưới đây, Xem xét trường hợp không hỗ trợ thoát được cho TS.

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

cho csv_content bạn có thể thử như bên dưới.

function b64DecodeUnicode(str: any) {        
        return decodeURIComponent(atob(str).split('').map((c: any) => {
            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        }).join(''));
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.