<前端老鸟>深挖 Blob和ArrayBuffer

693 阅读6分钟

Blob

一个blob对象就是一个包含有只读原始数据的类文件对象。blob对象中的数据并不一定得是JavaScript中的原生形式。file接口基于blob,继承了blob的功能,并且扩展支持了用户计算机上的本地文件

Blob(binary large object),二进制类文件大对象,是一个可以存储二进制文件的“容器”,HTML5中的Blob对象除了存放二进制数据外还可以设置这个数据的MIME类型。File接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。

new Blob([data], {type: "application/octet-binary"})

Blob构造函数接受两个参数,第一个参数是一个包含实际数据的数组,第二个参数是数据的MIME类型。

  • Blob.size blob对象的数据大小

  • Blob.type 表示blob对象所包含数据的MIME类型。如果实例化时未指明类型,则该值为空字符串。

简单介绍下MIME和扩展名的区别:

MIME类型(多用途互联网邮件扩展Multipurpose Internet Mail Extensions):标识邮件和http请求的网络数据格式。 扩展名:操作系统中标识文件。

Blob.slice

相当于数组Array.slice方法,表示截取指定范围的数据,形成新的blob对象。 此方法返回一个新的Blob对象,包含了原blob对象中指定范围内的数据

  • Blob.slice(start:number,end:number,contentType:string)
  • start:开始索引,默认为0
  • end:截止结束索引(不包括end)
  • contentType:新blob的MIME类型,默认为空字符串

Blob.slice的主要用途是分片上传文件

分片上传逻辑如下:

获取要上传文件的File对象,根据chunk(每片大小)对文件进行分片

通过post方法轮循上传每片文件,其中url中拼接querystring用于描述当前上传的文件信息;post body中存放本次要上传的二进制数据片段

接口每次返回offset,用于执行下次上传

或者不用返回offset,前端利用文件内容hash成md5,将md5一起发给后端, 然后,后端拿到数据后也将文件内容hasd成md5,比较两者md5的一致性

简单的实现分片上传:

initUpload();//初始化上传
function initUpload(){
    var chunk = 100 * 1024;  //每片大小    
    var input = document.getElementById("file");    //input file
    input.onchange = function(e){
        var file = this.files[0];
        var query = {};
        var chunks = [];
        if (!!file) {
            var start = 0;//文件分片            
            for (var i = 0; i < Math.ceil(file.size / chunk); i++) {
                var end = start + chunk;
                chunks[i] = file.slice(start , end);
                start = end;
            }
            // 采用post方法上传文件            
            // url query上拼接以下参数,用于记录上传偏移            
            // post body中存放本次要上传的二进制数据            
            query = {
                fileSize: file.size,
                dataSize: chunk,
                nextOffset: 0 
            }
            upload(chunks, query, successPerUpload);
        }
    }
}
// 执行上传
function upload(chunks, query, cb){
    var queryStr = Object.getOwnPropertyNames(query).map(key=> {
        return key + "=" + query[key];
    }).join("&");
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "http://xxxx/opload?" + queryStr);
    xhr.overrideMimeType("application/octet-stream");
    //获取post body中二进制数据    
    var index = Math.floor(query.nextOffset / query.dataSize);
    getFileBinary(chunks[index], function(binary){
        if (xhr.sendAsBinary) {
            xhr.sendAsBinary(binary);
        } else {
            xhr.send(binary);
        }
    });
    xhr.onreadystatechange = function(e){
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
            	// 接口返回nextoffset
                var resp = JSON.parse(xhr.responseText);      
                resp = { 
                isFinish:false,      
                offset:100*1024       
                }    
                if (typeof cb === "function") {
                    cb.call(this, resp, chunks, query)
                }
            }
        }
    }
}
// 每片上传成功后执行
function successPerUpload(resp, chunks, query){
    if (resp.isFinish === true) {
        alert("上传成功");
    } else {
        //未上传完毕        
        query.offset = resp.offset;
        upload(chunks, query, successPerUpload);
    }
}
// 获取文件二进制数据
function getFileBinary(file, cb){
    var reader = new FileReader();
    reader.readAsArrayBuffer(file);
    reader.onload = function(e){
        if (typeof cb === "function") {
            cb.call(this, this.result);
        }
    }
}

Blob的应用

图片上传

<input type="file" value="点我" id="inputImg" onchange="changeHandle(this)">
<img id="image">

let changeHandle = (file) =>{
    var reader = new FileReader();
    reader.onload = function(evt){
        document.getElementById('image').src = evt.target.result;
        image = evt.target.result;
    }
    //  读Blob为base64
    reader.readAsDataURL(file.files[0]);
}

图片下载

let changeHandle = (file) =>{
    //  转化为url下载地址
    let url = URL.createObjectURL(file.files[0])
    let a = document.createElement('a')
    a.setAttribute('download', '图片')
    a.href = url
    a.click()
}

ajax请求

axios.get('https://xxxxxx', {responseType: 'blob'})
.then(res => {
    let url = URL.createObjectURL(res.data)
    let a = document.createElement('a')
    a.setAttribute('download', '图片')
    a.href = url
    a.click()
})

window.URL.createObjectURL(),该方法接受File对象和Blob对象。

这里有一篇单独说明URL.createObjectURL的文章

ArrayBuffer

ArrayBuffer对象表示内存中一段原始的二进制数据容器(缓冲区)。 ArrayBuffer对象也是二进制数据的容器,它可以让javascript去直接操作内存,定义二进制数据,ArrayBuffer不能直接操作二进制数据,ArrayBuffer对象代表原始的二进制数据,它只代表内存的二进制数据,如果要操作这些数据,需要引入视图,就是将二进制的数据转化为特定数据内容的"数组"(类数组)。

为什么要引入二进制操作呢?

这个接口的原始设计目的,与 WebGL 项目有关。所谓 WebGL,就是指浏览器与显卡之间的通信接口,为了满足 JavaScript 与显卡之间大量的、实时的数据交换,它们之间的数据通信必须是二进制的,而不能是传统的文本格式。文本格式传递一个 32 位整数,两端的 JavaScript 脚本与显卡都要进行格式转化,将非常耗时。这时要是存在一种机制,可以像 C 语言那样,直接操作字节,将 4 个字节的 32 位整数,以二进制形式原封不动地送入显卡,脚本的性能就会大幅提升。

ArrayBuffer核心

二进制数组由三类对象组成。

(1)ArrayBuffer对象:代表内存之中的一段二进制数据,可以通过“视图”进行操作。“视图”部署了数组接口,这意味着,可以用数组的方法操作内存。

(2)TypedArray视图:共包括 9 种类型的视图,比如Uint8Array(无符号 8 位整数)数组视图, Int16Array(16 位整数)数组视图, Float32Array(32 位浮点数)数组视图等等。

(3)DataView视图:可以自定义复合格式的视图,比如第一个字节是 Uint8(无符号 8 位整数)、第二、三个字节是 Int16(16 位整数)、第四个字节开始是 Float32(32 位浮点数)等等,此外还可以自定义字节序。

简单说,ArrayBuffer对象代表原始的二进制数据,TypedArray视图用来读写简单类型的二进制数据,DataView视图用来读写复杂类型的二进制数据。

更多ArrayBuffer内容,请浏览这里

通过ArrayBuffer,我们可以去直接去操作二进制文件,可以修改和新增,删除

总结

Blob和ArrayBuffer都能存储二进制数据。

Blob相对而言储存的二进制数据大(如File文件对象),Blob对象表示一个不可变、原始数据的类文件对象。

ArrayBuffer对象表示原始的二进制数据缓冲区,即在内存中分配指定大小的二进制缓冲区(容器),用于存储各种类型化数组的数据,是最基础的原始数据容器,无法直接读取或写入, 需要通过具体视图来读取或写入,即TypedArray对象或DataView对象对内存大小进行读取或写入。 两者是独立的,并不是谁继承谁,但是两者的目的是一样的,去操作二进制文件,ArrayBuffer更加细腻化,而Blob可以快速实现一些基本功能.两者可以相互转化。 ArrayBuffer是原始的二进制数据缓冲区,不能设置MIME类型。 Blob可以储存大量的二进制编码格式的数据,可以设置对象的MIME类型。

相互转化

Blob => ArrayBuffer

let blob = new Blob([1,2,3,4])
let reader = new FileReader();
reader.onload = function(result) {
    console.log(result);
}
reader.readAsArrayBuffer(blob);

ArrayBuffer => Blob

let blob = new Blob([buffer]);