一直以来,JS都没有比较好的直接处理二进制的方法。Blob的存在,允许我们可以通过JS直接操作二进制数据。Blob 对象表示一个二进制文件的数据内容,比如一个图片文件的内容就可以通过 Blob 对象读写。通常用来读写文件,它的名字是 Binary Large Object (二进制大型对象)的缩写。

Blob对象是一个包含有只读原始数据的类文件对象。Blob对象中的数据并不一定是JavaScript中的原生形式。File接口基于Blob,继承Blob的功能,并且扩展支持用户计算机上的本地文件。 它与 ArrayBuffer 的区别在于,它用于操作二进制文件,而 ArrayBuffer 用于操作内存。

构造Blob对象

浏览器原生提供Blob()构造函数,用来生成实例对象。

  1. new Blob(dataArray [, options]) // dataArray可以是任意多个ArrayBuffer,ArrayBufferView, Blob,或者 DOMString对象。

生成Blob对象有两种方法:一种是使用Blob构造函数,另一种是对已有的Blob对象使用slice()方法切出一段。

Blob构造函数

创建出完整数据,Blob的属性:size和type,表示数据的大小和类型;

  1. let blob = new Blob(['I am Blob'],{type: 'text/html'});
  2. console.log(blob); // size: 9; type: "text/html"

slice() 创建

可以用于大文件的切割分片处理。
Blob具有一个实例方法slice,用来拷贝原来的数据,返回一个新的Blob对象,包含源Blob对象中指定范围内的数据。

  1. Blob.slice([start[, end[, contentType]]])
  2. let newBlob = blob.slice(3); // size: 6

参数说明:

  • start 可选,开始索引,可以为负数,语法类似于数组的slice方法.默认值为0.
  • end 可选,结束索引,可以为负数,语法类似于数组的slice方法.默认值为最后一个索引.
  • contentType可选 ,新的Blob对象的MIME类型,这个值将会成为新的Blob对象的type属性的值,默认为一个空字符串.

    URL 创建Blob URL链接

    createObjectURL

    URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的URL 对象表示指定的 File 对象或 Blob 对象。
    1. window.URL = window.URL || window.webkitURL;
    2. objectURL = URL.createObjectURL(blob);
    URL.createObjectURL()函数可以创建一个Blob URL,参数blob是用来创建URL的File对象或者Blob对象,返回值格式是:blob://URL。

    在每次调用 createObjectURL() 方法时,都会创建一个新的 URL 对象,即使你已经用相同的对象作为参数创建过。当不再需要这些 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL() 方法传入创建的URL为参数,用来释放它。浏览器会在文档退出的时候自动释放它们,但是为了获得最佳性能和内存使用状况,应该在安全的时机主动释放掉它们。

revokeObjectURL()

URL.revokeObjectURL() 静态方法用来释放一个之前通过调用 URL.createObjectURL() 创建的已经存在的 URL 对象。

  1. window.URL = window.URL || window.webkitURL; // 兼容处理
  2. URL.revokeObjectURL(objectURL);

Blob的使用

下载文件

  1. // html
  2. <a download="data.txt" id="getData">下载</a>
  3. // JavaScript
  4. var data= 'Hello world!';
  5. var blob = new Blob([data], {
  6. type: 'text/html,charset=UTF-8'
  7. });
  8. window.URL = window.URL || window.webkitURL;
  9. document.querySelector("#getData").href = URL.createObjectURL(blob);

生成图片

  1. var droptarget = document.getElementById('droptarget');
  2. droptarget.ondrop = function (e) {
  3. var files = e.dataTransfer.files;
  4. for (var i = 0; i < files.length; i++) {
  5. var type = files[i].type;
  6. if (type.substring(0,6) !== 'image/')
  7. continue;
  8. var img = document.createElement('img');
  9. img.src = URL.createObjectURL(files[i]);
  10. img.onload = function () {
  11. this.width = 100;
  12. document.body.appendChild(this);
  13. URL.revokeObjectURL(this.src);
  14. }
  15. }
  16. }

上面代码通过为拖放的图片文件生成一个 URL,产生它们的缩略图,从而使得用户可以预览选择的文件。

读取文件

取得 Blob 对象后,可以通过FileReader对象,读取 Blob 对象的内容,即文件内容。
FileReader 对象提供四个方法,处理 Blob 对象。Blob 对象作为参数传入这些方法,然后以指定的格式返回。

  • FileReader.readAsText():返回文本,需要指定文本编码,默认为 UTF-8。
  • FileReader.readAsArrayBuffer():返回 ArrayBuffer 对象。
  • FileReader.readAsDataURL():返回 Data URL。
  • FileReader.readAsBinaryString():返回原始的二进制字符串。

    1. // HTML 代码如下
    2. <input type="file" onchange="readfile(this.files[0])"></input>
    3. <pre id="output"></pre>
    4. // javascript
    5. function readfile(f) {
    6. var reader = new FileReader();
    7. reader.readAsText(f);
    8. reader.onload = function () {
    9. var text = reader.result; // 读取文件的内容,以text格式显示到页面
    10. var out = document.getElementById('output');
    11. out.innerHTML = '';
    12. out.appendChild(document.createTextNode(text));
    13. }
    14. reader.onerror = function(e) {
    15. console.log('Error', e);
    16. };
    17. }

    通过指定 FileReader 实例对象的onload监听函数,在实例的result属性上拿到文件内容
    下边例子,用于读取二进制文件。

    1. // HTML 代码如下
    2. <input type="file" onchange="typefile(this.files[0])"></input>
    3. // JavaScript
    4. function typefile(file) {
    5. // 文件开头的四个字节,生成一个 Blob 对象
    6. var slice = file.slice(0, 4);
    7. var reader = new FileReader();
    8. // 读取这四个字节
    9. reader.readAsArrayBuffer(slice);
    10. reader.onload = function (e) {
    11. var buffer = reader.result;
    12. // 将这四个字节的内容,视作一个32位整数
    13. var view = new DataView(buffer);
    14. var magic = view.getUint32(0, false);
    15. // 根据文件的前四个字节,判断它的类型
    16. switch(magic) {
    17. case 0x89504E47: file.verified_type = 'image/png'; break;
    18. case 0x47494638: file.verified_type = 'image/gif'; break;
    19. case 0x25504446: file.verified_type = 'application/pdf'; break;
    20. case 0x504b0304: file.verified_type = 'application/zip'; break;
    21. }
    22. console.log(file.name, file.verified_type);
    23. };
    24. }

    分片上传

    分片上传逻辑如下:

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

  • 通过post方法轮循上传每片文件,其中url中拼接querystring用于描述当前上传的文件信息;post body中存放本次要上传的二进制数据片段
  • 接口每次返回offset,用于执行下次上传 ```javascript 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; }

  1. // 采用post方法上传文件
  2. // url query上拼接以下参数,用于记录上传偏移
  3. // post body中存放本次要上传的二进制数据
  4. query = {
  5. fileSize: file.size,
  6. dataSize: chunk,
  7. nextOffset: 0
  8. }
  9. upload(chunks, query, successPerUpload);
  10. }
  11. }

}

// 执行上传 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”);

  1. //获取post body中二进制数据
  2. var index = Math.floor(query.nextOffset / query.dataSize);
  3. getFileBinary(chunks[index], function (binary) {
  4. if (xhr.sendAsBinary) {
  5. xhr.sendAsBinary(binary);
  6. } else {
  7. xhr.send(binary);
  8. }
  9. });
  10. xhr.onreadystatechange = function (e) {
  11. if (xhr.readyState === 4) {
  12. if (xhr.status === 200) {
  13. var resp = JSON.parse(xhr.responseText);
  14. // 接口返回nextoffset
  15. // resp = {
  16. // isFinish:false,
  17. // offset:100*1024
  18. // }
  19. if (typeof cb === "function") {
  20. cb.call(this, resp, chunks, query)
  21. }
  22. }
  23. }
  24. }

}

// 每片上传成功后执行 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); } } }

  1. <a name="nuzt1"></a>
  2. ### 隐藏视频源路径
  3. ```javascript
  4. var video = document.getElementById('video');
  5. var obj_url = window.URL.createObjectURL(blob);
  6. video.src = obj_url;
  7. video.play()
  8. window.URL.revokeObjectURL(obj_url);

Web Worker 串行加载优化

一般形式 :
main.js:

  1. const worker = new Worker('worker.js');
  2. worker.addEventListener('message', function(evt) {
  3. console.log(`[main] result is: ${evt.data.result}.`);
  4. }, false);
  5. worker.postMessage({num1: 20, num2: 10});
  6. console.log('[main] Main is initialized.');

worker.js:

  1. self.addEventListener('message', function (evt) {
  2. const num1 = evt.data.num1;
  3. const num2 = evt.data.num2;
  4. const result = num1 + num2;
  5. console.log('[worker] num1=' + num1 + ', num2=' + num2);
  6. self.postMessage({result: result});
  7. }, false);
  8. console.log(`[worker] Worker is initialized.`);

使用 Blob、URL.createObjectURL 优化后:

  1. const workerFileContent = `self.addEventListener('message', function (evt) {
  2. const num1 = evt.data.num1;
  3. const num2 = evt.data.num2;
  4. const result = num1 + num2;
  5. console.log('[worker] num1=' + num1 + ', num2=' + num2);
  6. self.postMessage({result: result});
  7. }, false);`;
  8. const workerBlob = new Blob([workerFileContent], { type:'text/javascript' });
  9. const workerUrl = window.URL.createObjectURL(workerBlob);
  10. const worker = new Worker(workerUrl);
  11. window.URL.revokeObjectURL(workerUrl);
  12. worker.addEventListener('message', function(evt) {
  13. console.log(`[main] result is: ${evt.data.result}.`);
  14. }, false);
  15. worker.postMessage({num1: 20, num2: 10});