JavaScript 文件和二進(jìn)制數(shù)據(jù)的操作

2021-09-15 15:19 更新

歷史上,JavaScript無(wú)法處理二進(jìn)制數(shù)據(jù)。如果一定要處理的話,只能使用charCodeAt()方法,一個(gè)個(gè)字節(jié)地從文字編碼轉(zhuǎn)成二進(jìn)制數(shù)據(jù),還有一種辦法是將二進(jìn)制數(shù)據(jù)轉(zhuǎn)成Base64編碼,再進(jìn)行處理。這兩種方法不僅速度慢,而且容易出錯(cuò)。ECMAScript 5引入了Blob對(duì)象,允許直接操作二進(jìn)制數(shù)據(jù)。

Blob對(duì)象是一個(gè)代表二進(jìn)制數(shù)據(jù)的基本對(duì)象,在它的基礎(chǔ)上,又衍生出一系列相關(guān)的API,用來(lái)操作文件。

  • File對(duì)象:負(fù)責(zé)處理那些以文件形式存在的二進(jìn)制數(shù)據(jù),也就是操作本地文件;
  • FileList對(duì)象:File對(duì)象的網(wǎng)頁(yè)表單接口;
  • FileReader對(duì)象:負(fù)責(zé)將二進(jìn)制數(shù)據(jù)讀入內(nèi)存內(nèi)容;
  • URL對(duì)象:用于對(duì)二進(jìn)制數(shù)據(jù)生成URL。

Blob對(duì)象

Blob(Binary Large Object)對(duì)象代表了一段二進(jìn)制數(shù)據(jù),提供了一系列操作接口。其他操作二進(jìn)制數(shù)據(jù)的API(比如File對(duì)象),都是建立在Blob對(duì)象基礎(chǔ)上的,繼承了它的屬性和方法。

生成Blob對(duì)象有兩種方法:一種是使用Blob構(gòu)造函數(shù),另一種是對(duì)現(xiàn)有的Blob對(duì)象使用slice方法切出一部分。

(1)Blob構(gòu)造函數(shù),接受兩個(gè)參數(shù)。第一個(gè)參數(shù)是一個(gè)包含實(shí)際數(shù)據(jù)的數(shù)組,第二個(gè)參數(shù)是數(shù)據(jù)的類型,這兩個(gè)參數(shù)都不是必需的。

var htmlParts = ["<a id=\"a\"><b id=\"b\">hey!<\/b><\/a>"];

var myBlob = new Blob(htmlParts, { "type" : "text\/xml" });

下面是一個(gè)利用Blob對(duì)象,生成可下載文件的例子。

var blob = new Blob(["Hello World"]);

var a = document.createElement("a");
a.href = window.URL.createObjectURL(blob);
a.download = "hello-world.txt";
a.textContent = "Download Hello World!";

body.appendChild(a);

上面的代碼生成了一個(gè)超級(jí)鏈接,點(diǎn)擊后提示下載文本文件hello-world.txt,文件內(nèi)容為“Hello World”。

(2)Blob對(duì)象的slice方法,將二進(jìn)制數(shù)據(jù)按照字節(jié)分塊,返回一個(gè)新的Blob對(duì)象。

var newBlob = oldBlob.slice(startingByte, endindByte);

下面是一個(gè)使用XMLHttpRequest對(duì)象,將大文件分割上傳的例子。

function upload(blobOrFile) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', '/server', true);
  xhr.onload = function(e) { ... };
  xhr.send(blobOrFile);
}

document.querySelector('input[type="file"]').addEventListener('change', function(e) {
  var blob = this.files[0];

  const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk sizes.
  const SIZE = blob.size;

  var start = 0;
  var end = BYTES_PER_CHUNK;

  while(start < SIZE) {
    upload(blob.slice(start, end));

    start = end;
    end = start + BYTES_PER_CHUNK;
  }
}, false);

})();

(3)Blob對(duì)象有兩個(gè)只讀屬性:

  • size:二進(jìn)制數(shù)據(jù)的大小,單位為字節(jié)。
  • type:二進(jìn)制數(shù)據(jù)的MIME類型,全部為小寫,如果類型未知,則該值為空字符串。

在Ajax操作中,如果xhr.responseType設(shè)為blob,接收的就是二進(jìn)制數(shù)據(jù)。

FileList對(duì)象

FileList對(duì)象針對(duì)表單的file控件。當(dāng)用戶通過file控件選取文件后,這個(gè)控件的files屬性值就是FileList對(duì)象。它在結(jié)構(gòu)上類似于數(shù)組,包含用戶選取的多個(gè)文件。

<input type="file" id="input" onchange="console.log(this.files.length)" multiple />

當(dāng)用戶選取文件后,就可以讀取該文件。

var selected_file = document.getElementById('input').files[0];

采用拖放方式,也可以得到FileList對(duì)象。

var dropZone = document.getElementById('drop_zone');
dropZone.addEventListener('drop', handleFileSelect, false);

function handleFileSelect(evt) {
    evt.stopPropagation();
    evt.preventDefault();

    var files = evt.dataTransfer.files; // FileList object.

    // ...
}

上面代碼的 handleFileSelect 是拖放事件的回調(diào)函數(shù),它的參數(shù)evt是一個(gè)事件對(duì)象,該參數(shù)的dataTransfer.files屬性就是一個(gè)FileList對(duì)象,里面包含了拖放的文件。

File對(duì)象

File對(duì)象是FileList對(duì)象的成員,包含了文件的一些元信息,比如文件名、上次改動(dòng)時(shí)間、文件大小和文件類型。它的屬性值如下:

  • name:文件名,該屬性只讀。
  • size:文件大小,單位為字節(jié),該屬性只讀。
  • type:文件的MIME類型,如果分辨不出類型,則為空字符串,該屬性只讀。
  • lastModifiedDate:文件的上次修改時(shí)間。
var selected_file = document.getElementById('input').files[0];

var fileName = selected_file.name;
var fileSize = selected_file.size;
var fileType = selected_file.type;

FileReader對(duì)象

FileReader對(duì)象用于讀取文件,即把文件內(nèi)容讀入內(nèi)存。它的參數(shù)是File對(duì)象或Blob對(duì)象。

對(duì)于不同類型的文件,F(xiàn)ileReader使用不同的方法讀取。

  • readAsBinaryString(Blob|File):返回二進(jìn)制字符串,該字符串每個(gè)字節(jié)包含一個(gè)0到255之間的整數(shù)。

  • readAsText(Blob|File, opt_encoding) :返回文本字符串。默認(rèn)情況下,文本編碼格式是'UTF-8',可以通過可選的格式參數(shù),指定其他編碼格式的文本。

  • readAsDataURL(Blob|File):返回一個(gè)基于Base64編碼的data-uri對(duì)象。

  • readAsArrayBuffer(Blob|File) :返回一個(gè)ArrayBuffer對(duì)象。

readAsText方法用于讀取文本文件,它的第一個(gè)參數(shù)是File或Blob對(duì)象,第二個(gè)參數(shù)是前一個(gè)參數(shù)的編碼方法,如果省略就默認(rèn)為UTF-8編碼。該方法是異步方法,一般監(jiān)聽onload事件,用來(lái)確定文件是否加載結(jié)束,方法是判斷FileReader實(shí)例的result屬性是否有值。其他三種讀取方法,用法與readAsText方法類似。

var reader = new FileReader();

reader.onload = function(e) {
  var text = reader.result;
}

reader.readAsText(file, encoding);

readAsDataURL方法返回一個(gè)data URL,它的作用基本上是將文件數(shù)據(jù)進(jìn)行Base64編碼。你可以將返回值設(shè)為圖像的src屬性。

var reader = new FileReader();

reader.onload = function(e) {
  var dataURL = reader.result;
}

reader.readAsDataURL(file);

readAsBinaryString方法可以讀取任意類型的文件,而不僅僅是文本文件,返回文件的原始的二進(jìn)制內(nèi)容。這個(gè)方法與XMLHttpRequest.sendAsBinary方法結(jié)合使用,就可以使用JavaScript上傳任意文件到服務(wù)器。

var reader = new FileReader();

reader.onload = function(e) {
  var rawData = reader.result;
}

reader.readAsBinaryString(file);

readAsArrayBuffer方法讀取文件,返回一個(gè)類型化數(shù)組(ArrayBuffer),即固定長(zhǎng)度的二進(jìn)制緩存數(shù)據(jù)。在文件操作時(shí)(比如將JPEG圖像轉(zhuǎn)為PNG圖像),這個(gè)方法非常方便。

var reader = new FileReader();

reader.onload = function(e) {
  var arrayBuffer = reader.result;
}

reader.readAsArrayBuffer(file);

除了以上四種不同的讀取文件方法,F(xiàn)ileReader對(duì)象還有一個(gè)abort方法,用于中止文件上傳。

var reader = new FileReader();

reader.abort();

FileReader對(duì)象采用異步方式讀取文件,可以為一系列事件指定回調(diào)函數(shù)。

  • onabort方法:讀取中斷或調(diào)用reader.abort()方法時(shí)觸發(fā)。
  • onerror方法:讀取出錯(cuò)時(shí)觸發(fā)。
  • onload方法:讀取成功后觸發(fā)。
  • onloadend方法:讀取完成后觸發(fā),不管是否成功。觸發(fā)順序排在 onload 或 onerror 后面。
  • onloadstart方法:讀取將要開始時(shí)觸發(fā)。
  • onprogress方法:讀取過程中周期性觸發(fā)。

下面的代碼是如何展示文本文件的內(nèi)容。

var reader = new FileReader();

reader.onload = function(e){
  console.log(e.target.result);
}

reader.readAsText(blob);

onload事件的回調(diào)函數(shù)接受一個(gè)事件對(duì)象,該對(duì)象的target.result就是文件的內(nèi)容。

下面是一個(gè)使用readAsDataURL方法,為img元素添加src屬性的例子。

var reader = new FileReader();

reader.onload = function(e) {
    document.createElement('img').src = e.target.result;

};

reader.readAsDataURL(f);

下面是一個(gè)onerror事件回調(diào)函數(shù)的例子。

var reader = new FileReader();
reader.onerror = errorHandler;

function errorHandler(evt) {
  switch(evt.target.error.code) {
    case evt.target.error.NOT_FOUND_ERR:
      alert('File Not Found!');
      break;
    case evt.target.error.NOT_READABLE_ERR:
      alert('File is not readable');
      break;
    case evt.target.error.ABORT_ERR:
      break;
    default:
      alert('An error occurred reading this file.');
  };
}

下面是一個(gè)onprogress事件回調(diào)函數(shù)的例子,主要用來(lái)顯示讀取進(jìn)度。

var reader = new FileReader();
reader.onprogress = updateProgress;

function updateProgress(evt) {
  if (evt.lengthComputable) {
    var percentLoaded = Math.round((evt.loaded / evt.totalEric Bidelman) * 100);

    var progress = document.querySelector('.percent');
    if (percentLoaded < 100) {
      progress.style.width = percentLoaded + '%';
      progress.textContent = percentLoaded + '%';
    }
  }
}

讀取大文件的時(shí)候,可以利用Blob對(duì)象的slice方法,將大文件分成小段,逐一讀取,這樣可以加快處理速度。

綜合實(shí)例:顯示用戶選取的本地圖片

假設(shè)有一個(gè)表單,用于用戶選取圖片。

<input type="file" name="picture" accept="image/png, image/jpeg"/>

一旦用戶選中圖片,將其顯示在canvas的函數(shù)可以這樣寫:

document.querySelector('input[name=picture]').onchange = function(e){
     readFile(e.target.files[0]);
}

function readFile(file){

  var reader = new FileReader();

  reader.onload = function(e){
    applyDataUrlToCanvas( reader.result );
  };

  reader.reaAsDataURL(file);
}

還可以在canvas上面定義拖放事件,允許用戶直接拖放圖片到上面。

// stop FireFox from replacing the whole page with the file.
canvas.ondragover = function () { return false; };

// Add drop handler
canvas.ondrop = function (e) {
  e.stopPropagation();
  e.preventDefault(); 
  e = e || window.event;
  var files = e.dataTransfer.files;
  if(files){
    readFile(files[0]);
  }
};

所有的拖放事件都有一個(gè)dataTransfer屬性,它包含拖放過程涉及的二進(jìn)制數(shù)據(jù)。

還可以讓canvas顯示剪貼板中的圖片。

document.onpaste = function(e){
  e.preventDefault();
  if(e.clipboardData&&e.clipboardData.items){
    // pasted image
    for(var i=0, items = e.clipboardData.items;i<items.length;i++){
      if( items[i].kind==='file' && items[i].type.match(/^image/) ){
        readFile(items[i].getAsFile());
        break;
      }
    }
  }
  return false;
};

URL對(duì)象

URL對(duì)象用于生成指向File對(duì)象或Blob對(duì)象的URL。

var objecturl =  window.URL.createObjectURL(blob);

上面的代碼會(huì)對(duì)二進(jìn)制數(shù)據(jù)生成一個(gè)URL,類似于“blob:http%3A//test.com/666e6730-f45c-47c1-8012-ccc706f17191”。這個(gè)URL可以放置于任何通??梢苑胖肬RL的地方,比如img標(biāo)簽的src屬性。需要注意的是,即使是同樣的二進(jìn)制數(shù)據(jù),每調(diào)用一次URL.createObjectURL方法,就會(huì)得到一個(gè)不一樣的URL。

這個(gè)URL的存在時(shí)間,等同于網(wǎng)頁(yè)的存在時(shí)間,一旦網(wǎng)頁(yè)刷新或卸載,這個(gè)URL就失效。除此之外,也可以手動(dòng)調(diào)用URL.revokeObjectURL方法,使URL失效。

window.URL.revokeObjectURL(objectURL);

下面是一個(gè)利用URL對(duì)象,在網(wǎng)頁(yè)插入圖片的例子。

var img = document.createElement("img");

img.src = window.URL.createObjectURL(files[0]);

img.height = 60;

img.onload = function(e) {
    window.URL.revokeObjectURL(this.src);
}

body.appendChild(img);

var info = document.createElement("span");

info.innerHTML = files[i].name + ": " + files[i].size + " bytes";

body.appendChild(info);

還有一個(gè)本機(jī)視頻預(yù)覽的例子。

var video = document.getElementById('video');
var obj_url = window.URL.createObjectURL(blob);
video.src = obj_url;
video.play()
window.URL.revokeObjectURL(obj_url);

參考鏈接

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)