理解DOMString、Document、FormData、Blob、File、ArrayBuffer數據類型

一、XMLHttpRequest 2.0的家臣們

我大學那會兒,一個稱爲Ajax的東西對前端行業造成了深遠影響,不僅是JS語言,而包括前端地位、職位興起以及工作分工等。拋開IE6瀏覽器不談,其他瀏覽器的Ajax實際上都是藉助XMLHttpRequest實現的。

然後,好多年過去了,XMLHttpRequest帶着兩位家臣,DOMStringDocument數據類型攻城略地,幾乎一統天下。

然時代是發展的,人們羣衆的需求是旺盛的,HTML5猶如冉冉升起的新星開始普照大地,恩澤大衆。XMLHttpRequest由於就兩個家臣DOMStringDocument,且並不是100%聽話。因此,其已經開始hold不住HTML5的耀眼光芒了。爲了順應時代的潮流,XMLHttpRequest凹凸曼變身升級到2.0,變化諸多,其中一個很重要的變化就是廣招家臣,擴張實力,與HTML5一起完成千秋萬載之大業。

這些家臣有:DOMStringDocumentFormDataBlobFileArrayBuffer這些類型。也就是在XMLHttpRequest Level 2背景下,我們Ajax可以發送任意這些類型的數據。有了諸多忠實可靠的家臣,XMLHttpRequest Level 2猶如織田信長般勢不可擋,前途無量!

織田信長家臣有:羽柴秀吉、柴田勝家、明智光秀、竹中半兵衛、黑田官兵衛、織田信忠、瀧川一益、丹羽長秀、前田利家、池田恆興、佐久間信盛、森蘭丸、九鬼嘉隆

二、家臣之DOMString

跟着XMLHttpRequest闖南走北很多年,看名字似乎很囂張且高深莫測。實際上,在JavaScript中,DOMString就是String。規範解釋說DOMString指的是UTF-16字符串,而JavaScript正是使用了這種編碼的字符串,因此,在Ajax中,DOMString就等同於JS中的普通字符串。

大家應該都與XMLHttpRequest中數據返回屬性之responseText打過交道吧,按照我的理解,這廝就是與DOMString數據類型發生關係的,表明返回的數據是常規字符串。

三、家臣之Document數據類型

如果單純看Document對象,則解釋很多,在這裏,我們只要關注下圖標註的這一個:
responseXML

可以看到,實際上就是XMLHttpRequest中數據返回屬性之responseXML,也就是可以解析爲XML的數據。因此,這裏的Document數據類似你就可以近似看成XML數據類型。

DOMStringDocument都是XMLHttpRequest時代就跟隨的數據類型,元老級。下面這些數據類型都是XMLHttpRequest 2.0新增的,新招的家臣,各懷絕技哦!

四、家臣之FormData對象

XMLHttpRequest Level 2添加了一個新的接口FormData. 利用FormData對象,我們可以通過JavaScript用一些鍵值對來模擬一系列表單控件,我們還可以使用XMLHttpRequest的send()方法來異步的提交這個”表單”。比起普通的ajax, 使用FormData的最大優點就是我們可以異步上傳一個二進制文件。

以上爲官方口吻的解釋,略抽象。我們應該都用過jQuery,其中有個方法叫做serialize(), 作用就是表單序列化,也就是以查詢字符串形式獲得類表單post/get的數據給Ajax請求,例如:userid=123&username=zxx.

FormData對象的作用就類似於這裏的serialize()方法,不過FormData是瀏覽器原生的,且支持二進制文件,是個一眼就會讓人喜歡的很讚的東西!

兼容性如下:
formData兼容性 張鑫旭-鑫空間-鑫生活

IE10+瀏覽器已經良好支持了,下面要介紹的其他家臣也都是IE10+支持。

實際使用是作爲構造函數,如下:

new FormData ([可選]HTMLFormElement)

HTMLFormElement這個參數可選,可有可無。表示form表單元素,就是我們要序列化,要提交的那個表單元素。

例如:

var newFormData = new FormData(someFormElement);

newFormData就是someFormElement這個表單元素中所有鍵值對數據了。

您可以狠狠地點擊這裏:FormData對象與表單數據獲取demo

demo頁面爲一個普通的登錄表單,截圖如下:
demo表單截圖

點擊登錄執行Ajax登錄,不過這裏是採用FormData格式發送的。

相關JS代碼如下:

document.querySelector("#formData").addEventListener("submit", function(event) {
    var myFormData = new FormData(this);
    var xhr = new XMLHttpRequest();
    xhr.open(this.method, this.action);
    xhr.onload = function(e) {
        if (xhr.status == 200 && xhr.responseText) {
            // 顯示:'歡迎你,' + xhr.responseText;
            this.reset();
        }
    }.bind(this);
    // 發送FormData對象數據
    xhr.send(myFormData);
    // 阻止默認的表單提交
    event.preventDefault();
}, false);

我們打開工具查看下請求:
firebug查看請求
Chrome開發者工具查看

以上分別是Firebug和Chrome開發者工具查看的結果。

我們再看下傳統Ajax請求:
傳統Ajax POST firebug查看
傳統Ajax post Chrome下查看 張鑫旭-鑫空間-鑫生活

差異還是比較大的。
FormData提交格式的每個數據分三部分:

  • 第一部分也就是第一行,表示“分界線(boundary)”,我尚未深入研究這個分界線,不過,我沒估計錯的話,二進制大文件分隔傳輸時候,就是使用這個分界線。在webkit核心中,使用“——WebKitFormBoundary”加16位隨機Base64位編碼的字符串作爲分隔邊界。根據Firebug的顯示,Firefox中,似乎是使用很多個"-"加時間戳進行邊界分隔的。這裏的邊界的作用比較單純,可能就是把表單的這兩個字段作爲兩個獨立數據流傳輸。
  • 第二部分也就是第二行,表示內容配置,這裏都是統一的form-data(因爲是FormData對象格式提交的),然後緊跟着name鍵值。
  • 第三部分就是第三行,表示傳輸的值。

雖然前臺傳輸差異較大,但是,後臺的處理是可以一致的,例如,我這裏的PHP代碼就非常簡單:

<?php
    $username = $_POST['email'];
    if (isset($username) == true) {
        echo $username;
    } else {
        echo '';    
    }
?>

FormData對象還有一個方法,爲append()方法,可以人爲的給當前FormData對象添加一個鍵/值對。

語法如下:

void append(DOMString 鍵, Blob 值, [可選] DOMString 文件名);
void append(DOMString 鍵, DOMString 值);

語法第一行出現了Blob, 這是我們下面要介紹的家臣之一,您可以先記住,這是用來表示二進制文件的,後面的文件名可選,據說,如果缺省,且傳輸的是Blob對象,則會使用"blob"代替。
第二行就是比較常規的用法,DOMString這個家臣已經介紹了,在JavaScript中就是普通字符串的意思。因此,比方說我們要額外提交個token值,可能就是:

myFormData.append("token", "ce509193050ab9c2b0c518c9cb7d9556");

於是,後臺就可以get token這個值了。

大家自行補腦,我就不再撐篇幅了。

FormData無法字符串化,因爲,無法用做表單序列化。

五、家臣之Blob數據對象

一個Blob對象就是一個包含有隻讀原始數據的類文件對象。Blob對象中的數據並不一定得是JavaScript中的原生形式。File接口基於Blob, 繼承了Blob的功能,並且擴展支持了用戶計算機上的本地文件。

創建Blob對象的方法有幾種,可以調用Blob構造函數,還可以使用一個已有Blob對象上的slice()方法切出另一個Blob對象,還可以調用canvas對象上的toBlob方法。

以上爲MDN上官方口吻的解釋。實際上,Blob是計算機界通用術語之一,全稱寫作:BLOB (binary large object),表示二進制大對象。MySql/Oracle數據庫中,就有一種Blob類型,專門存放二進制數據。

在實際Web應用中,Blob更多是圖片二進制形式的上傳與下載,雖然其可以實現幾乎任意文件的二進制傳輸。

舉個例子,使用Blob從服務器上GET某mm的圖片(只要關心標紅的部分):

var xhr = new XMLHttpRequest();    
xhr.open("get", "mm1.jpg", true);
xhr.responseType = "blob";
xhr.onload = function() {
    if (this.status == 200) {
        var blob = this.response;  // this.response也就是請求的返回就是Blob對象
        var img = document.createElement("img");
        img.onload = function(e) {
          window.URL.revokeObjectURL(img.src); // 清除釋放
        };
        img.src = window.URL.createObjectURL(blob);
        eleAppend.appendChild(img);    
    }
}
xhr.send();

您可以狠狠地點擊這裏:Blob獲取圖片並二進制顯示demo

我們查看demo頁面這個mm圖片元素,會發現其URL地址既不是傳統HTTP,也不是Base64 URL,而是Blob形式~如下截圖示意:
圖片blob地址示意
demo頁面圖片的blob格式的URL

這就是Blob在Web開發中非常重要的一個功能——創建Blob網址。上述代碼涉及XMLHttpRequest 2一些重要知識點,以及window.URL相關技術,都是可以深入挖掘學習的部分,但,不是本文重點,以後有機會會細緻闡述。

但是,並不是所有的圖片都能以Blob形式請求,因爲,畢竟是Ajax請求嘛,還是有一定的跨域限制。XMLHttpRequest 2雖然支持跨源資源共享(CORS),但是,還是需要對Access-Control-Allow-Origin的設置,允許來自那個域名的這類請求,例如,允許本人的站點Blob請求你服務器上的圖片資源,你可以設置:

Access-Control-Allow-Origin: http://zhangxinxu.com

要允許任何域向您提交請求,可以設置:

Access-Control-Allow-Origin: *

我們都知道CSS3的font-face屬性,在Firefox瀏覽器下,如果字體文件跨域(包括跨子域),是顯示不出來的,也是通過

Access-Control-Allow-Origin: *

設置解決。其實,本質是一樣的。

由於權限原因,我的個人站點無法配置Access-Control-Allow-Origin,我測試了下,新浪微博的圖片是無法二進制請求的,不過我的前東家,xiaomishu.com的圖片都是可以Ajax請求並Blob顯示的,悄悄告訴大家,是我當初動的手腳,(*^__^*) 嘻嘻……

屬性
Blob對象有兩個屬性,參見下表:

屬性名 類型 描述
size unsigned long long(表示可以很大的數值) Blob對象中所包含數據的大小。字節爲單位。 只讀。
type DOMString 一個字符串,表明該Blob對象所包含數據的MIME類型。例如,上demo圖片MIME類似就是”image/jpeg“. 如果類型未知,則該值爲空字符串。 只讀。

今天在微博上看到一個表單提交之前判斷文件大小並作阻止的tip,實際上,就是使用的Blob對象的size屬性。

構造函數
與FormData對象類似,Blob也有一個構造函數用法。語法如下:

Blob Blob(
  [可選] Array parts,
  [可選] BlobPropertyBag properties
);

例如:

var myBlob= new Blob(arrayBuffer);

其中,兩個參數的含義是:

parts

一個數組,包含了將要添加到Blob對象中的數據。數組元素可以是任意多個的ArrayBuffer, ArrayBufferView(typed array), Blob, 或者DOMString對象。

properties

一個對象,設置Blob對象的一些屬性。目前僅支持一個type屬性,表示Blob的類型。

方法
Blob對象有個很重要的方法-slice(),作用是,可以實現文件的分割!

這個slice()有一段不堪回首的歷史,不過現在大家不要關心。目前的slice()方法已經跟JS中數組啊,字符串的slice方法用法一致了。如下:

Blob slice(
  [可選] long long start,
  [可選] long long end,
  [可選] DOMString contentType
};

參數釋義:

顯然,此方法返回的數據格式還是Blob對象,不過是指定範圍複製的新的Blob對象。注意,如果start參數的值比源Blob對象的size屬性值還大,則返回的Blob對象的size值爲0,也就是不包含任何數據。

六、家臣之File對象

File顧名思意就是“文件”,通常而言,表示我們使用file控件(<input type="file">)選擇的FileList對象,或者是使用拖拽操作搞出的DataTransfer對象。

這裏的File對象也是二進制對象,因此,從屬於Blob對象,Blob對象的一些屬性與方法,File對象同樣適合,且推薦使用Blob對象的屬性與方法。

File對象自身也有一些屬性與方法,但是,有些已經過時——不推薦使用,因此,當前很多HTML5 Ajax文件上傳下載的教程中出現是屬性和方法都是過時的,不要盲目Copy,請大家明辨!

屬性

File.lastModifiedDate[只讀]

文件對象最後修改的日期

File.name[只讀]

文件對象的名稱

File.fileName[只讀] [過時不推薦使用]

文件對象的名稱(請使用File.name代替)

File.fileSize[只讀] [過時不推薦使用]

文件對象的大小(請使用Blob.size代替)

Blob.size[只讀]

Blob對象包含數據的字節大小

Blob.type[只讀]

一個字符串,表明該Blob對象所包含數據的MIME類型

方法

File.getAsBinary()[過時不推薦使用]

二進制形式返回文件數據(請使用FileReader對象的FileReader.readAsBinaryString()方法代替)

File.getAsDataURL()[過時不推薦使用]

返回文件data:URL編碼字符串數據(請使用FileReader對象的FileReader.readAsDataURL()方法代替)

File.getAsText(string encoding)[過時不推薦使用]

以給定的字符串編碼返回文件數據解釋後的文本(請使用FileReader對象的FileReader.readAsText()方法代替)

Blob.size[只讀]

Blob對象包含數據的字節大小

Blob.type[只讀]

一個字符串,表明該Blob對象所包含數據的MIME類型。

上面有提到FileReader對象,這貨是相當的有貨,之前有人曾問我,如何將圖片轉換成Data base64 url格式,其中一個方法就是FileReader.readAsDataURL()方法(還有就是canvas元素的toDataURL()toDataURLHD()方法),然與本文主旨無關,暫不贅述;如您有興趣,頁面底部有其相關知識點鏈接,可自行概覽。

七、家臣之ArrayBuffer對象

//zxx:ArrayBuffer對象牽扯知識點非常多,這裏僅接觸肌膚,深入接觸下次會專門再說下。

很術語的解釋有:

ArrayBuffer表示二進制數據的原始緩衝區,該緩衝區用於存儲各種類型化數組的數據。

ArrayBuffer是二進制數據通用的固定長度容器。

所謂術語,就是小白看不懂的解釋語。我再用通俗語解釋下,希望大家可以有點感性的認識:

術語中,提到“二進制”,我們腦中應該會出現01010111之類;提到“緩衝”,會聯想到在線視頻提前加載一部分視頻的那個緩衝。但是,兩個合起來,“二進制數據緩衝區”,腦補就不連貫了,焦慮產生~~

現在,聽我的,上面概念全部扔掉。所謂ArrayBuffer就是個裝着2進制數據的對象。或者想象成帶了個名叫“緩衝”帽子的二進制數據。然後直接關聯:ArrayBuffer = 2進制

上面表示關聯,不是相等,諸位。

例如,我們設置Ajax請求的responseType爲”arraybuffer“,我們去請求某mm圖片,返回的response就是ArrayBuffer,就是個二進制對象。什麼緩衝不緩衝的,千萬別補腦這個。

如果還覺得概念抽象,可以看下面的具體認知:
大家可能玩過神器編輯器Sublime Text, 我們隨便找張圖片拖進去,會發現是類似下面這樣子的代碼:
Sublime Text圖片16進制編碼顯示

Sublime Text以16進制的形式顯示圖片資源,ArrayBuffer的差別在於是二進制,因此,我們可以把ArrayBuffer的形體腦補成——上圖的數字全是的0101 1000 1101之類的。Get it否?

上面提到的Blob對象也是二進制,那BlobArrayBuffer有啥區別呢?

Blob可以append ArrayBuffer數據,也就是Blob是個更高一級的大分類,類似領導的感覺。ArrayBuffer則是具有某種惡魔果實的尖兵。

ArrayBuffer存在的意義就是作爲數據源提前寫入在內存中,就是提前釘死在某個區域,長度也固定,萬年不變。於是,當我們要處理這個ArrayBuffer中的二進制數據,例如,分別8位,16位,32位轉換一遍,這個數據都不會變化,3種轉換共享數據。

So,ArrayBuffer就是緩衝出來的打死不動的二進制對象。

注意,ArrayBuffer本身是不能讀寫的,需要藉助類型化數組或DataView對象來解釋原始緩衝區(宰割原始二進制數據)。

類型化數組
類型化數組(Typed Arrays)是JavaScript中新出現的一個概念,專爲訪問原始的二進制數據而生。

類型數組的類型有:

名稱 大小 (以字節爲單位) 說明

Int8Array

1

8位有符號整數

Uint8Array

1

8位無符號整數

Int16Array

2

16位有符號整數

Uint16Array

2

16位無符號整數

Int32Array

4

32位有符號整數

Uint32Array

4

32位無符號整數

Float32Array

4

32位浮點數

Float64Array

8

64位浮點數

本質上,類型化數組和ArrayBuffer是一樣的。不過一個可讀寫(脫掉buffer限制),一個當數據源的命。

舉一些代碼例子,看看本質一致在何處:

// 創建一個8字節的ArrayBuffer  
var b = new ArrayBuffer(8);  
  
// 創建一個指向b的視圖v1,採用Int32類型,開始於默認的字節索引0,直到緩衝區的末尾  
var v1 = new Int32Array(b);  
  
// 創建一個指向b的視圖v2,採用Uint8類型,開始於字節索引2,直到緩衝區的末尾  
var v2 = new Uint8Array(b, 2);  
  
// 創建一個指向b的視圖v3,採用Int16類型,開始於字節索引2,長度爲2  
var v3 = new Int16Array(b, 2, 2);  

上面代碼裏變量的數據結構如下表所示:

變量 索引
  字節(不可索引)
b= 0 1 2 3 4 5 6 7
  類型數組
v1= 0 1
v2=     0 1 2 3 4 5
v3=     0 1    

由於類型化數組直接訪問固定內存,因此,速度很贊,比傳統數組要快!因爲普通Javascript數組使用的是Hash查找方式。同時,類型化數組天生處理二進制數據,這對於XMLHttpRequest 2、canvas、webGL等技術有着先天的優勢。

DataView對象
DataView對象在可以在ArrayBuffer中的任何位置讀取和寫入不同類型的二進制數據。

用法語法如下:

var dataView = new DataView(DataView(buffer, byteOffset[可選], byteLength[可選]);

其中,buffer表示ArrayBuffer;byteOffset指緩衝區開始處的偏移量(以字節爲單位);byteLength指緩衝區部分的長度(以字節爲單位)。

屬性

buffer

表示ArrayBuffer

byteOffset

指緩衝區開始處的偏移量

byteLength

指緩衝區部分的長度

方法有很多,實際上,是有規律的,篇幅原因,也不是重點,就單純露個臉:
getInt8getUint8getInt16getUint16getInt32getUint32getFloat32getFloat64setInt8setUint8setInt16setUint16setInt32setUint32setFloat32setFloat64.

下面回到ArrayBuffer對象ArrayBuffer對象自身也可以構造,跟上面的FormData, Blob對象類似,例如:

var buf = new ArrayBuffer(32);

語法爲:

ArrayBuffer ArrayBuffer(length[可以很大數值]);

我們在控制檯運行下new ArrayBuffer(32),看看結果:
ArrayBuffer的屬性和方法 張鑫旭-鑫空間-鑫生活

可以看到,其有一個byteLength屬性,表示ArrayBuffer的長度,也可以說是大小;還有一個slice方法,語法如下:

ArrayBuffer slice(
  begin
  end[可選]
);

begin表示起始,end表示結束點。據說,Internet Explorer 10 以及iOS6-是沒有該方法的。

綜上,舉個ArrayBuffer的實例吧,發送使用XMLhttpRequest發送ArrayBuffer數據:

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

  var uInt8Array = new Uint8Array([1, 2, 3]);

  xhr.send(uInt8Array.buffer);
}

使用了類型化數組,發送的是類型化數組(uInt8Array)的buffer屬性,也就是ArrayBuffer對象。

over~

八、結束語

新技術層出不窮,我覺得吧,以後,行業的分支可能要更細了。比方說JS開發吧,可能就有JS UI交互開發工程師;JS Web開發工程師。因爲,一個人想要完全hold住這麼多的知識點,還真不是一般人能做到的。

剛開始寫的時候,還想最後舉個文件分割上傳的例子,只可惜內容實在太多,加上去也會被湮沒,於是作罷,決定有機會,專門講下這個。還有FileReader可以獨立講一下,還有類型化數組也可以專門講一下等。

學路漫漫,任重道遠。文中若有致命的結論錯誤或疏忽的文字書寫錯誤,都歡迎指正,不甚感謝。歡迎討論,歡迎交流!

參考鏈接

  • https://developer.mozilla.org/zh-CN/docs/DOM/DOMString
  • https://developer.mozilla.org/en-US/docs/Web/API/document
  • https://developer.mozilla.org/zh-CN/docs/DOM/XMLHttpRequest/FormData
  • https://developer.mozilla.org/zh-CN/docs/DOM/Blob
  • https://developer.mozilla.org/en-US/docs/Web/API/File
  • https://developer.mozilla.org/en-US/docs/Web/API/FileReader
  • http://technet.microsoft.com/zh-cn/ie/br212474
  • https://developer.mozilla.org/en-US/docs/Web/API/ArrayBuffer
  • http://blog.csdn.net/hfahe/article/details/7421203

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章