JS关于文件的处理

Blob

Blob,Binary Large Object的缩写,代表二进制类型的大对象。Mysql中的Blob类型就表示二进制数据的容器,在Web中,Blob对象是二进制数据,但它是类似文件对象的二进制数据,因此可以操作File对象一样操作Blob对象,实际上,File继承自Blob

Blob、File、ArrayBuffer、FileList、FileReader、DataURL、BlobURL

  • Blob和ArrayBuffer都是用来存储二进制的,Blob是对象,ArrayBuffer是数组,由于ArrayBuffer是一个二进制数组,所以可以作为Blob对象的参数:
//为
hello world
的二进制 const u8Buf = new Uint8Array([60, 100, 105, 118, 62, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 60, 47, 100, 105, 118, 62]); const u8Blob = new Blob([u8Buf], { type: "text/html" }); // Blob {size: 22, type: "text/html"}
  • ArrayBuffer不能直接操作,依赖于TypedArray视图(例如上面的Uint8Array)或者DataView对象来解释原始缓冲区。
  • Blob对象可以直接通过slice进行内容分片,其本身只有size和type属性。
  • File对象继承于Blob对象,并提供了name(文件名)、size(大小)、type(MIME类型)、lastModified、lastModifiedDate等信息。
  • FileReader用于异步读取文件内容(用于读取File、Blob的内容)
    • FileReader.readAsArrayBuffer(blob):开始读取指定Blob中的内容,一旦完成,result属性中保存的数据是被读取文件的ArrayBuffer数据。(将blob转换成ArrayBuffer)
    • FileReader.readAsBinaryString(blob):开始读取Blob内容,一旦完成,result属性中将包含所读取文件的原始二进制数据。
    • FileReader.readAsDataURL(blob):开始读取指定Blob内容,一旦完成,result属性中将包含一个data:URL格式的字符串以表示所读取文件的内容(将blob转换成DataURL,转换成base64编码)
    • FileReader.readAsText():开始读取指定Blob内容,一旦完成,result属性中将包含一个字符串表示所读取的文件内容
    • FileReader.readyState
      • EMPTY:0 还没有加载任何数据
      • LOADING:1数据正在被加载
      • DONE:2已完成全部的读取请求
    • FileReader.onload:事件,该事件在读取操作完成时触发
    • FileRader.onerror:事件,该事件在读取操作发生错误时触发。
  • DataURL由FileReader.readAsDataURL(blob)生成,为一整串base64编码是一个完整的数据。BlobURL由window.URL.createObjectURL(blob)生成,是一个类似于HTTP的URL
  • FileList通常用于表单提交文件时,为文件的类数组对象

    



Blob基本用法

创建

通过Blob的构造函数创建Blob对象:

Blob(blobParts[,options])
  • blobParts:为数组,数组中的每一项连接起来构成Blob对象的数据,数组中的每项元素可以是ArrayBuffer,ArrayBufferView,Blob,DOMString。
  • options:可选项,置顶MIME类型和结束符方式
    • type,默认值为 "",它代表了将会被放入到blob中的数组内容的MIME类型。
    • endings,默认值为"transparent",用于指定包含行结束符\n的字符串如何被写入。 它是以下两个值中的一个: "native",表示行结束符会被更改为适合宿主操作系统文件系统的换行符; "transparent",表示会保持blob中保存的结束符不变。
    var data1 = "a";
    var data2 = "b";
    var data3 = "
This is a blob
"; var data4 = { "name": "abc" }; var blob1 = new Blob([data1]); var blob2 = new Blob([data1, data2]); var blob3 = new Blob([data3]); var blob4 = new Blob([JSON.stringify(data4)]); var blob5 = new Blob([data4]); var blob6 = new Blob([data3, data4]); console.log(blob1); //输出:Blob {size: 1, type: ""} console.log(blob2); //输出:Blob {size: 2, type: ""} console.log(blob3); //输出:Blob {size: 44, type: ""} console.log(blob4); //输出:Blob {size: 14, type: ""} console.log(blob5); //输出:Blob {size: 15, type: ""} console.log(blob6); //输出:Blob {size: 59, type: ""}

size代表Blob对象中所包含数据的字节数
使用字符串和使用对象创建Blob是不同的,例如blob4通过JSON.stringify把data4对象转换成JSON字符串,而blob5则直接使用对象创建,两个blob对象的size分别为14和15。
blob4的结果为"{"name":"abc"}"刚好是14个字节。
blob5的记过为"[object Object]"是15个字节。
实际上,当使用普通对象创建Blob对象时,相当于调用了普通对象的toString()方法得到字符串数据,然后在再创建Blob对象。

slice分片方法

Blob对象有一个sloce方法,放回一个新的Blob对象,包含了源Blob对象中范围内的数据。

slice([start[,end[,contentType]]])
  • start:起始下标,表示第一个会被拷贝进新的Blob字节的其实位置。如果是一个负数,那么这个偏移量将会从数据的末尾从后道歉开始计算。
  • end:结束下标,如果传入负数,偏移量会从数据的末尾从后到前开始计算。
  • contentType:新的Blob对象的文档类型,默认值为一个空的字符串。
var data = "abcdef"
var blob1 = new Blob([data])
var blob2 = blob1.slice(0,3)

//输出:Blob {size:6,type:""}
console.log(blob1);
//输出:Blob {size:3,type:""}
console.log(blob2);

Blob使用场景

文件分片上传

File继承自Blob,所以我们可以用slice方法对大文件进行分片长传

function uploadFile(file){
    //每片大小为1M
    var chunkSize = 1024*1024
    var totalSize = file.size
    //分片总数
    var chunckQuantity = Math.ceil(totalSize/chunkSize)
    //偏移量
    var offset = 0 
    var reader = new FileReader()
    //设置文件onload回调
    reader.onload = function(e){
        var xhr = new XMLHttpRequest()
        xhr.open("POST","http://xxx/upload?fileName="+file.name)
        xhr.overrideMimeType("application/octet-stream")

        xhr.onreadystatechange = function(){
            if(xhr.readState === XMLHttpRequest.DONE && xhr.status === 200){
                ++offset
                if(offset === chunkQuantity){
                    //上传完成
                }else if(offset === chunckQuantity){
                    //上传最后一片,偏移量结束点为文件大小
                    blob = file.slice(offset*chunckSize,totalSize)
                    reader.readAsBinaryString(blob)
                }else{
                    blob = file.slice(offset*chunckSzie,(offset+1)*chunckSize)
                    reader.readAsBinaryString(blob)
                }
            }else{
                alert("上传出错")
            }
        }
        if(xhr.sendAsBinary){
            //e.target.result为此次读取的分片二进制数据
            xhr.sendAsBinary(e.target.result)
        }else{
            xhr.send(e.targt.result)
        }
    }
    var blob = file.slice(0, chunkSize)
    reader.readAsBinaryString(blob)
}

可以进一步丰富,比如上传进度,使用多个XMLHttpRequest对象并行上传对象(需要传递分片数据的位置参数给服务端)等。

Blob URL(资源地址)

Blob URL是Blob协议的URL:

blob:http://xxx

Blob URL可以通过URL.createObjectURL(blob)创建,在绝大部分场景下,我们可以像使用HTTP协议的URL一样,使用Blob URL。

常见的场景有:作为文件的下载地址和作为图片资源地址。

作为文件的下载地址:





    
    Blob Test
    



    下载




点击下载按钮,浏览器将会下载一个名为file的文件,文件内容是Blob Data。通过Blob对象,在前端就可以动态生成文件,提供浏览器下载。

image

作为图片资源地址





    
    Blob Test
    



    
    

在network标签栏下能够发现这个Blob URL的请求信息

Blob URL和Data URL的区别

还可以使用Data URL方式加载图片资源:





    
    Blob Test
    



    
    

FileReader的readAsDataURL生成一个Data URL,如图所示:


image

web性能优化中有一项措施,把小图片用base64编码直接迁入到HTML文件中,实际上就是利用了Data URL来获取嵌入的图片数据。

Blob URL和Data URL的区别

  • Blob URL的长度一般比较短,但Data URL因为直接存储图片base64编码后的数据,往往很长,如上图所示,浏览器在显示Data URL时使用了省略号(…)。当显式大图片时,使用Blob URL能获取更好的可能性
  • Blob URL可以方便的使用XMLHttpRequest获取源数据
var blobUrl = URL.createObjectURL(new Blob(['Test'], {type: 'text/plain'}));
var x = new XMLHttpRequest();
// 如果设置x.responseType = 'blob',将返回一个Blob对象,而不是文本:
// x.responseType = 'blob';
x.onload = function() {
    alert(x.responseText);   // 输出 Test
};
x.open('get', blobUrl);
x.send();
  • Blob URL只能在当前应用内部使用,把Blob URL复制到浏览器的地址中,是无法获取数据的(外部无法获取)。而Data URL可以在浏览器中使用,具有较好的移植性

指定文件类型

除了可以用作图片资源的网络地址,Blob URL也可以用作其他资源的网络地址,例如html文件、json文件等,为了保证浏览器能正确的解析Blob URL返回的文件类型,需要在创建Blob对象时指定相应的type

// 创建HTML文件的Blob URL
var data = "
This is a blob
"; var blob = new Blob([data], { type: 'text/html' }); var blobURL = URL.createObjectURL(blob); // 创建JSON文件的Blob URL var data = { "name": "abc" }; var blob = new Blob([JSON.stringify(data)], { type: 'application/json' }); var blobURL = URL.createObjectURL(blob);

Blob和ArrayBuffer

ArrayBuffer对象用来表示通用的、固定长度的原始二进制数据缓冲区。
通过new ArrayBuffer(length)来获得一片连续的内存空间,它不能直接读写,但可根据需要将其传递到TypedArray视图或者DataView对象来解释原始缓冲区。
实际上视图只是给我们提供了一个某种类型的读写接口,让我们可以操作ArrayBuffer里的数据。
TypedArray需要制定一个数组类型来保证数组成员都是一个数据类型,而DataView数组成员可以是不同的数据类型。

TypedArray视图的类型数组对象:(他们的构造函数都接收一个ArrayBuffer参数进行转换,由于ArrayBuffer不能直接读取)

  • Int8Array:8位有符号整数,长度1个字节。
  • Uint8Array:8位无符号整数,长度1个字节。
  • Uint8ClampedArray:8位无符号整数,长度1个字节,溢出处理不同。
  • Int16Array:16位有符号整数,长度2个字节。
  • Uint16Array:16位无符号整数,长度2个字节。
  • Int32Array:32位有符号整数,长度4个字节。
  • Uint32Array:32位无符号整数,长度4个字节。
  • Float32Array:32位浮点数,长度4个字节。
  • Float64Array:64位浮点数,长度8个字节。

Blob与ArrayBuffer的区别是,除了原始字节以外它还提供了Mime type作为原数据,Blob和ArrayBuffer之间可以进行转换,File对象其实继承自Blob对象,并提供了name、lastModifiedDate、size、type等基础元数据

Blob对象转换成ArrayBuffer

//创建一个以二进制数据存储的html文件
const text = "
hello world
" const blob = new Blob([text],{type:"text/html"}) //以文本读取 const textReader = new FileReader() textReader.readAsText(blob) textReader.onload = function(){ console.log(textReader.result);//
hello word
} //以ArrayBuffer读取 const bufReader = new FileReader() bufReader.readAsArrayBuffer(blob) bufReader.onload = function(){ console.log(new Uint8Array(bufReader.result)) // Uint8Array(22) [60, 100, 105, 118, 62, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 60, 47, 100, 105, 118, 62] }

ArrayBuffer转换成Blob

const u8Buf = new Uint8Array([60, 100, 105, 118, 62, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 60, 47, 100, 105, 118, 62]);
const u8Blob = new Blob([u8Buf], { type: "text/html" }); // Blob {size: 22, type: "text/html"}
const textReader = new FileReader();

textReader.readAsText(u8Blob);
textReader.onload = function() {
  console.log(textReader.result); // 同样得到div>hello world
};

从后台获取Blob(File)

通过正确的设置responseType我们可以直接获取到Blob对象

function ajax(url,cb){
    const xhr = new XMLHttpRequest()
    xhr.open("get",url)
    //"text"-字符串 "blob"-Blob对象 "arraybuffer"-ArrayBuffer对象
    xhr.responseType = "blob"
    xhr.onload = function(){
        cb(xhr.response)
    }
    xhr.send
}

通过请求一个Blob对象或者ArrayBuffer再转换成Blob对象,再通过URL.createObjectURL生成BlobURL赋值给src属性即可

ajax('video.mp4', function(res){
    const src = URL.createObjectURL(res); 
    video.src = src;
})

MediaSource(流媒体播放,视频流)

video标签src指向一个视频地址,视频播完了再将src修改为下一段的视频地址然后播放,这显然不符合我们无缝播放的要求。其实有了我们前面Blob URL的学习,我们可能就会想到一个思路,用Blob URL指向一个视频二进制数据,然后不断将下一段视频的二进制数据添加拼接进去。这样就可以在不影响播放的情况下,不断的更新视频内容并播放下去,想想是不是有点流的意思出来了。

要实现这个功能我们要通过MediaSource来实现,MediaSource接口功能也很纯粹,作为一个媒体数据容器可以和HTMLMediaElement进行绑定。基本流程就是通过URL.createObjectURL创建容器的BLob URL,设置到video标签的src上,在播放过程中,我们仍然可以通过MediaSource.appendBuffer方法往容器里添加数据,达到更新视频内容的目的。

可以理解MediaSource为一个Blob的容器,可以通过addSourceBuffer来创建一个指定类型的Blob容器,这个容器可以通过appendBuffer不断的往里面添加数据

const video = document.querySelector('video');
//视频资源存放路径,假设下面有5个分段视频 video1.mp4 ~ video5.mp4,第一个段为初始化视频init.mp4
const assetURL = "http://www.demo.com";
//视频格式和编码信息,主要为判断浏览器是否支持视频格式,但如果信息和视频不符可能会报错
const mimeCodec = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'; 
if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {
  const mediaSource = new MediaSource();
  video.src = URL.createObjectURL(mediaSource); //将video与MediaSource绑定,此处生成一个Blob URL
  mediaSource.addEventListener('sourceopen', sourceOpen); //可以理解为容器打开
} else {
  //浏览器不支持该视频格式
  console.error('Unsupported MIME type or codec: ', mimeCodec);
}

function sourceOpen () {
  const mediaSource = this;
  const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
  let i = 1;
  function getNextVideo(url) {
    //ajax代码实现翻看上文,数据请求类型为arraybuffer
    ajax(url, function(buf) {
      //往容器中添加请求到的数据,不会影响当下的视频播放。
      sourceBuffer.appendBuffer(buf);
    });
  }
  //每次appendBuffer数据更新完之后就会触发
  sourceBuffer.addEventListener("updateend", function() {
    if (i === 1) {
      //第一个初始化视频加载完就开始播放
      video.play();
    }
    if (i < 6) {
      //一段视频加载完成后,请求下一段视频
      getNextVideo(`${assetURL}/video${i}.mp4`);
    }
    if (i === 6) {
      //全部视频片段加载完关闭容器
      mediaSource.endOfStream();
      URL.revokeObjectURL(video.src); //Blob URL已经使用并加载,不需要再次使用的话可以释放掉。
    }
    i++;
  });
  //加载初始视频
  getNextVideo(`${assetURL}/init.mp4`);
};

你可能感兴趣的:(JS关于文件的处理)