浏览器、node中进行base64编解码

2,337 阅读6分钟

Base64 是一组相似的二进制到文本(binary-to-text)的编码规则,使得二进制数据在解释成 radix-64 的表现形式后能够用 ASCII 字符串的格式表示出来。Base64 这个词出自一种 MIME 数据传输编码。

浏览器

现代浏览器原生支持base64的编解码,在控制台和js worker中都可使用,其提供了2个方法:atob() btoa(),分别用于解码和编码.

btoa()

String 对象中创建一个base-64编码的ASCII字符串,其中字符串中的每个字符都被视为一个二进制数据字节。

由于这个函数将每个字符视为二进制数据的字节,而不管实际组成字符的字节数是多少,所以如果任何字符的码位超出 0x00 ~ 0xFF 这个范围,则会引发 InvalidCharacterError 异常。请参阅 Unicode_字符串 ,该示例演示如何编码含有码位超出 0x00 ~ 0xFF 范围的字符的字符串。

因此,该函数只能将ascll字符串编码成base64格式,对于ascll以外的字符串则会报错

浏览器和node中使用base64编解码

错误提示要编码的字符中包含了latin1编码以外的字符,这些字符是不能被编码的。

latin1: Latin-1 代表 ISO-8859-1。 此字符编码仅支持从 U+0000U+00FFUnicode 字符。 每个字符使用单个字节进行编码。 超出该范围的字符会被截断,并映射成该范围内的字符。

解决方法是将字符串中的非ascll字符进行转义,然后再进行编码

// unicode to base64
function utoa(str) {
    return window.btoa(unescape(encodeURIComponent(str)));
}

上述代码来源于MDN文档WindowOrWorkerGlobalScope.btoa()

encodeURIComponent用于将字符串中非ascll字符转义成格式为"%加字符的ASCII码"的十六进制转义序列,常用于URI的处理。

处理后的字符串完全由ascll字符组成,再由unescape()将其中的十六进制转义序列转换为其表示的字符,最后再由btoa()编码成base64,不过这里的unescape()是非必要的,一方面该方法已被web标准弃用,二是经过转义的字符串已经能够被btoa()编码。

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

浏览器和node中使用base64编解码-2

// unicode to base64 by 
function utoa(str) {
    return window.btoa(unescape(encodeURIComponent(str)));
}
// base64 to unicode
function atou(str) {
    return decodeURIComponent(escape(window.atob(str)));
}

atob()

与上面同理,直接使用atob()转换一个不是全由ascll字符组成的字符串编码成的base64字符串时,会报与上述同样的错误。

MDN文档WindowOrWorkerGlobalScope.btoa()的方法

// base64 to unicode
function atou(str) {
    return decodeURIComponent(escape(window.atob(str)));
}

escape()是非必要的

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

Polyfill

有不少PC项目还需要兼容IE9以下版本,所以,我们可以专门针对这些浏览器再引入一段ployfill脚本或者一个JS文件即可。

github.com/MaxArt2501/…

另一个ployfill实现

github.com/davidchambe…

// Polyfill from  https://github.com/MaxArt2501/base64-js/blob/master/base64.js
(function() {
    // base64 character set, plus padding character (=)
    var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

        // Regular expression to check formal correctness of base64 encoded strings
        b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;

    window.btoa = window.btoa || function(string) {
        string = String(string);
        var bitmap, a, b, c,
            result = "",
            i = 0,
            rest = string.length % 3; // To determine the final padding

        for (; i < string.length;) {
            if ((a = string.charCodeAt(i++)) > 255 ||
                (b = string.charCodeAt(i++)) > 255 ||
                (c = string.charCodeAt(i++)) > 255)
                throw new TypeError("Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.");

            bitmap = (a << 16) | (b << 8) | c;
            result += b64.charAt(bitmap >> 18 & 63) + b64.charAt(bitmap >> 12 & 63) +
                b64.charAt(bitmap >> 6 & 63) + b64.charAt(bitmap & 63);
        }

        // If there's need of padding, replace the last 'A's with equal signs
        return rest ? result.slice(0, rest - 3) + "===".substring(rest) : result;
    };

    window.atob = window.atob || function(string) {
        // atob can work with strings with whitespaces, even inside the encoded part,
        // but only \t, \n, \f, \r and ' ', which can be stripped.
        string = String(string).replace(/[\t\n\f\r ]+/g, "");
        if (!b64re.test(string))
            throw new TypeError("Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.");

        // Adding the padding if missing, for semplicity
        string += "==".slice(2 - (string.length & 3));
        var bitmap, result = "",
            r1, r2, i = 0;
        for (; i < string.length;) {
            bitmap = b64.indexOf(string.charAt(i++)) << 18 | b64.indexOf(string.charAt(i++)) << 12 |
                (r1 = b64.indexOf(string.charAt(i++))) << 6 | (r2 = b64.indexOf(string.charAt(i++)));

            result += r1 === 64 ? String.fromCharCode(bitmap >> 16 & 255) :
                r2 === 64 ? String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255) :
                String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255, bitmap & 255);
        }
        return result;
    };
})()

类型化数组转base64

有时候后端传给我们一个二进制流的文件资源,我们需要使用一个类型化数组来进行存储和读取。

ArrayBuffer我们叫它类型化数组,它的诞生就是为了解决一个问题:操作二进制数据。

只由01组成的二进制数据往往是非常巨大的,上千个字节可以说司空见惯,传统的Array这时候处理起二进制数据起来就显得非常低效,所以ArrayBuffer出现了,它作为一块专用的内存区域存放在栈中,取数据非常快。

我们现在通过new ArrayBuffer(10)初始化一个buffer实例,看看会得到什么。

let buffer = new ArrayBuffer(10);
console.log(buffer);

ArrayBuffer(10) {}
[[Int8Array]]: Int8Array(10) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[[Int16Array]]: Int16Array(5) [0, 0, 0, 0, 0]
[[Uint8Array]]: Uint8Array(10) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
byteLength: 10
__proto__: ArrayBuffer

可以看到在ArrayBuffer中,主要存放了几个视图Int8Array表示8位有符号整数数组,Int16Array表示16位有符号整数数组,Uint8Array则表示8位无符号整数数组。

当然,如果比如说我们想取出Int8Array这个数组来,是不能直接通过buffer.Int8Array来取的。这是因为ArrayBuffer不能直接通过下标去读写,我们需要构造相关类型数组实例。

const myUint8Array = new Uint8Array(buffer)

得到数组值后,怎样才能将其转换成base64呢? 很简单,使用String.fromCharCode这个函数,它接受的参数为一堆代码单元序列,输出一个普通字符串。而我们刚刚得到的类型化数组,里面存放的正是代码单元。

const myStr = String.fromCharCode(...myUint8Array)

现在类型数组里的值已经被全部转换成字符串了,我们再使用btoa()将其编码成base64字符串

node

node环境中虽然没有提供专门方法去进行base64的编解码,但是我们能利用buffer.toString()很轻松的完成,不仅仅是处理字符串,处理文件也非常方便。

处理字符串

function stringToBase64(str){
    let base64Str = Buffer.from(str).toString('base64')
    return base64Str;
}

function base64ToString(base64Str){
    let str = Buffer.from(base64Str,'base64').toString()
    return str
}

处理文件

举个栗子

对一幅图片进行base64编解码

//编码
const fs = require('fs')
const fileToBase64  = fs.readFileSync('./img.png')
console.log(fileToBase64.toString('base64'))

//输出
iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAgAElEQVR4Xuy9CZQdZ3Uu+tVcdeah5251t8bWZEuyJGxjecJDGHMJYEISwiPAu7DyXt66YSUvyc3wlEByIXkk910ePCC5gSQ3IYlJwg0EG4ONsS3Pkm1Jlq1Z6kE9nXmqU/Nbe1cdWZiEaAqms+p4tVtSV9X5z66/vt7Dt78tIH7FFogtEFtghVhAWCHrjJcZWyC2QGwBxIAVb4LYArEFVowFYsBaMbcqXmhsgdgCMWDFeyC2QGyBFWOBGLBWzK2KFxpbILZADFjxHogtEFtgxVggBqwVc6vihcYWiC0QA1a8B2ILxBZYMRaIAWvF3Kp4obEFYgvEgBXvgdgCsQVWjAViwFoxtypeaGyB2AIxYMV7ILZAbIEVY4EYsFbMrYoXGlsgtkAMWPEeiC0QW2DFWCAGrBVzq+KFxhaILRADVrwHYgvEFlgxFogBa8XcqnihsQViC8SAFe...

//解码
const base64Tofile = Buffer.from(fileToBase64.toString('base64'),'base64')
//输出为img2.png
fs.writeFileSync('img2.png', base64Tofile);

编码后的文件要能被浏览器识别,必须根据其 MIME type加上相应类型的前缀,如png图片:data:image/png;base64,txt文本:data:text/plain;base64,

js-base64库

github.com/dankogai/js…

详情请看github文档,使用起来也非常方便