文件操作一直是早期浏览器的痛点,全封闭式的不给JavaScript操作的空间。随着H5新接口的推出这个壁垒被打破了。历史上JavaScript是无法处理二进制数据的,如果一定要处理也只能使用charCodeAt()
方法,一个一个字节地从字符编码转换为二进制数据。另一种方式是将二进制数据转换为Base64
编码再进行处理。这两种方式不仅速度慢而且容易出错。因此ECMScript引入了Blob
对象运行直接操作二进制数据。
二进制大数据对象 Blob
H5中的Blob
对象全称为 Binary Large Object(二进制大数据对象),是一个代表二进制数据的基础对象。Blob
对象表示一个不可变、原始数据的类文件对象,Blob
表示的不一定是JavaScript原生格式的数据。简单来说,Blob
是一种JavaScript的对象类型。比如,H5的文件操作对象File
就是Blob
的一个分支或一个子集。Blob
自身存储大量的二进制数据。
Blob
对象只是二进制数据的容器,本身并不能操作二进制。在其基础上衍生出了一系列的API来操作文件。衍生对象都是建立在Blob
对象基础上,并继承了Blob
对象的属性和方法。Blob
可视为一个原始数据对象,它内置的slice()
方法可以读取原始二进制数据中的某块区域。
H5中的Blob对象和MySQL中的Blob类型在概念上是有区别的,MySQL中的Blob类型只是一个二进制数据容器,而H5中的Blob对象除了存放二进制数据外,还可以设置二进制数据的MIME类型,这相当于对文件的存储。
衍生对象 | 功能 |
---|---|
File 对象 |
负责处理以文件形式存在的二进制数,即操作本地文件。 |
FileList 对象 |
File 对象的网页表单接口 |
FileReader 对象 |
负责将二进制数据读入内存 |
URL 对象 |
使用二进制数据生成URL |
生成Blob
对象有两种方式:一种是使用Blob
的构造函数,另一种是对现有的Blob
对象使用slice()
方法切割出一部分。
构造函数
Blob(blobParts, options)
Blob
构造函数接收两个可选参数,返回一个新创建的Blob
对象。
参数 | 描述 |
---|---|
blobParts |
包含实际数据的数组 |
options |
数据的MIME类型 |
const blobParts = ['<a id="link"><strong id="text">content</strong></a>'];
let options = {};
options.type = "text/xml";
let blob = new Blob(blobParts, options);
console.log(blob);//Blob(51) {size: 51, type: "text/xml"}
Blob
对象只有两个只读属性
属性 | 描述 |
---|---|
size |
二进制数据的大小,单位字节。 |
type |
二进制数据的MIME类型,小写,若类型未知则为空字符串。 |
例如:使用Blob
对象生成可下载文件
const blobParts = ["hello world"];
let options = {};
let blob = new Blob(blobParts, options);
let link = document.createElement("a");
link.href = window.URL.createObjectURL(blob);
link.textContent = "download";
link.download = "test.txt";
const body = document.querySelector("body");
body.append(link);
代码生成的连接
<a href="blob:http://127.0.0.1:8080/b3f8cde1-ebc0-4051-bb1f-1ea533a0f435" download="test.txt">download</a>
方法
方法 | 功能 |
---|---|
Blob.slice([start[, end[, contentType]]]) |
切片分割返回新的Blob对象,包含源Blob对象中指定范围内的数据。 |
Blob.stream() |
- |
Blob.text() |
- |
Blob.arrayBuffer() |
- |
分块处理
Blob.slice([start[, end[, contentType]]])
使用Blob
对象的slice()
方法将二进制数据按照字节分块,分块后会返回一个新的Blob
对象。
let blobParts = ["123456789"];
let blob1 = new Blob(blobParts);
console.log(blob1);//Blob(9) {size: 9, type: ""}
const start = 1;
const end = 5;
const contentType = "string";
let blob2 = blob1.slice(start, end, contentType);
console.log(blob2);//Blob(4) {size: 4, type: "string"}
Blob
的slice()
方法截取的是文件数据二进制字符,也相当于数据,因为二进制字符表示的就是数据本身。slice()
方法可理解为切片分割,也就是将Blob
中保存的二进制数据拆分为一块一块的。当上传大文件时,由于上传文件大小限制,就需要使用slice()
方法进行切片,将文件分片上传。
文件读取器 FileReader
从Blob
对象中提取数据的唯一方法是使用FileReader
文件读取器,通过使用FileReader
的方法可以将Blob
读取为字符串或数据URL
。
//创建Blob对象
const blob = new Blob(["data"], {type:"text/plain"});
//创建文件读取器对象
let fileReader = new FileReader();
//开始读取文件
fileReader.readAsText(blob);
//数据读取完毕后自动触发onload事件
//文件读取器的result属性存储着读取文件内容
fileReader.onload = () => console.log(fileReader.result);//data
FileReader
对象接受File
对象或Blob
对象作为参数,用于读取文件的实际内容,也就是把文件内容读入到内存。对于不同类型的文件,FileReader
文件读取器会使用不同的方式读取。
FileReader
文件读取器用于文件内容读入内存,通过一系列异步接口,可以在主线程中访问本地文件。使用FileReader
对象Web应用程序可以异步的读取存储在用户计算机上的本地文件或原始数据缓冲内容。可以使用File
对象或Blob
对象来指定所需要处理的文件或二进制数据。
方法
FileReader
文件读取器提供四种不同读取文件的方式
方法 | 描述 |
---|---|
abort():void |
终止读取文件操作 |
readAsArrayBuffer(blob|file):void |
异步按字节读取并返回ArrayBuffer 内存二进制缓冲对象 |
readAsBinaryString(blob|file):void |
异步按字节读取并返回二进制字符串(已废弃) |
readAsDataURL(blob|file):void |
异步读取文件并返回data:url 字符串 |
readAsText(blob|file, encoding):void |
异步按字节读取并返回字符串形式 |
readAsDataURL
FileReader.readAsDataURL(File|Blob):void
readAsDataURL
读取的结果是一个基于Base64编码的data-uri
对象。readAsDataURL
异步读取文件内容,返回结果使用data:url
的字符串形式表示,返回结果会进行Base64编码后输出,可用于读取图片。
DataURL
data:[<mime type>][;charset=<charset>][;base64],<encoded data>
格式 | 名称 | 描述 |
---|---|---|
data |
协议头 | 标识内容为一个data URI 资源 |
mime type |
MIME类型 | 表示此串内容的展现方式,客户端会以这个MIME类型来解析数据。 |
charset |
编码设置 | 默认编码为charset=US-ASCII 即数据部分的每个字符都会自动编码为%xx 。 |
base64 |
编码设定 | 可选项 |
encoded data |
Data URI承载内容 | 可以为纯文本也可以是经Base64编码后的内容 |
例如:
data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7
协议为 data
,并告诉客户端将这个内容作为 image/gif
格式来解析,需要解析的内容使用的是 base64
编码。它直接包含了内容但并没有一个确定的资源地址。协议后面的内容,可以告诉客户端一个准确下载资源的地址,而URI并不一定包含一个地址信息。
Data URIs定义的字符串内容可以作为小文件被插入到其它文档中。URI是Uniform Resource Identifier统一资源标识符的缩写,URI定义了接受内容的协议以及附带的相关内容,如果附带的相关内容是一个地址,那么此时的URI也是一个URL(Uniform Resource Locator,统一资源定位符)。
例如:
ftp://192.168.1.100/path/to/filename.ext
http://host.com/source/id
使用Data URL来呈现较长的内容,如二进制数据编码、图片等,采用Base64可以让内容变得更加简短。对图片来说,在GZIP压缩之后Base64图片实际上比原图GZIP压缩要大,体积增加大约1/3,使用时需要权衡。
Blob对象与Data URl中Base64字符串相互转换
/**
* Blob二进制大数据对象转化为DataURI格式的Base64编码
* @param {object} blob 二进制大数据对象
* @param {*} callback 回调函数
*/
function blobToDataURI(blob, callback){
let fileReader = new FileReader();
fileReader.readAsDataURL(blob);
fileReader.onload = function(evt){
callback(evt.target.result);
};
}
/**
* Data URI字符串转换为Blob对象
* @param {string} dataURI Data URI
*/
function dataURIToBlob(dataURI){
//获取MIME类型
const mime = dataURI.split(",")[0].split(":")[1].split(";")[0];
//获取Base64编码
const base64 = atob(dataURI.split(",")[1]);
//创建缓冲数组
const arrayBuffer = new ArrayBuffer(base64);
//创建视图
let intArray = new Uint8Array(arrayBuffer);
for(let i=0; i<base64; i++){
intArray[i] = base64.charCodeAt(i);
}
//创建Blob对象
return new Blob([intArray], {type:mime});
}
例如:在线预览本地图片
img
标签的src
属性或background
的url
属性可以通过赋值为图片网络地址或Base64
的方式来显示图片。文件上传中,一般会先将本地文件上传到服务器,上传成功后由服务器返回图片的网络地址再到前端显示。通过FileReader
的readAsDataURL
方法可以不经过后台直接将本地图片显示在页面上。这样做可以减少前后端频繁的交互过程以减少服务器无用的图片资源。
$ vim index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<input type="file" id="file" />
<img src="" alt="" id="img" />
<script src="index.js"></script>
</body>
</html>
$ vim index.js
//获取图片元素
const imgEle = document.getElementById("img");
//获取DOM元素并注册事件
const fileEle = document.getElementById("file");
fileEle.onchange = function(){
//获取文件对象
const file = this.files[0];
//文件对象存在则处理读取内容
if(!!file){
//创建文件读取器
let fileReader = new FileReader();
//读取本地文件图片并转换为Base64编码
fileReader.readAsDataURL(file);
//读取完毕后自动调用
fileReader.onload = function(){
//获取文件内容结果
console.log(this.result);
//查看文件内容字节大小
console.log(new Blob([this.result]));
//设置显示图片
imgEle.src = this.result;
};
}
}
readAsText
FileReader.readAsText(File|Blob, encoding="UTF-8"):void
readAsText
读取结果是一个文本字符串,默认情况下文本编码格式是UTF-8
,可以通过可选的编码格式encoding
参数指定其它编码格式的文本。
readAsText
需指定输出内容的字符编码格式后异步按字节读取文件内容,返回结果使用字符串形式表示,用于读取文本。对于媒体文件如图片、音频、视频等,由于内部组成并非按字符排列,使用readAsText
读取输出时会产生乱码。
例如:读取本地文本文件text.txt
内容
//获取DOM元素并注册事件
const ele = document.getElementById("file");
ele.onchange = function(){
//获取文件对象
const file = this.files[0];
//文件对象存在则处理读取内容
if(!!file){
//创建文件读取器
let fileReader = new FileReader();
//读取本地文件并以UTF-8编码方式输出
fileReader.readAsText(file, "utf-8");
//读取完毕后自动调用
fileReader.onload = function(){
//获取文件内容结果
console.log(this.result);//this is a test text file
//查看文件内容字节大小
console.log(new Blob([this.result]));//Blob {size: 24, type: ""}
};
}
}
readAsBinaryString(已废弃)
FileReader.readAsBinaryString(Blob|File):void
readAsBinaryString
读取结果是二进制字符串,每个字节包含一个0到255之间的整数。与readAsText
不同的是readAsBinaryString
会按字节读取文件内容,由于二进制数据只能被机器识别,若向对外可见还需要进行再次编码。readAsBinaryString
读取的结果则是二进制编码后的内容。尽管readAsBinaryString
可以按字节方式读取文件,但由于读取后的内容被编码为字符,因此大小会受到影响,所以不适合直接用于网络传输。
readAsArrayBuffer
FileReader.readAsArrayBuffer(File|Blob):void
readAsArrayBuffer
读取结果是一个ArrayBuffer
二进制缓冲区对象,与readAsBinaryString
不同的是readAsArrayBuffer
方式会按字节读取文件内容并转换为ArrayBuffer
对象,文件读取后的大小与源大小则保持一致,这也就是二者之间的区别。
readAsArrayBuffer
读取文件后会在内存中创建一个ArrayBuffer
二进制缓冲区的对象,然后将读取的二进制数据放入其中。通过此种方式就可以直接在网络中传输二进制数据。
例如:使用H5的XHR2
直接上传或下载二进制内容,无需通过form
标签由后端拉取二进制内容。
操作步骤
- 通过
input[type=file]
标签获取本地文件对象File
- 通过
FileReader
的readAsArrayBuffer
方法将File
对象转换为ArrayBuffer
数组缓冲区对象 - 创建
AJAX
的XHR
对象并配置请求参数 - 通过
XHR
对象的sendAsBinary
方法将文件的ArrayBuffer
内容填充至POST
的body
体后向服务器发送
const url = "";
//获取DOM元素并注册事件
const fileEle = document.getElementById("file");
fileEle.onchange = function(){
//获取文件对象
const file = this.files[0];
//文件对象存在则处理读取内容
if(!!file){
//创建文件读取器
let fileReader = new FileReader();
//读取本地文件并转换为内存数组缓冲区对象
fileReader.readAsArrayBuffer(file);
//读取完毕后自动调用
fileReader.onload = function(){
//查看文件内容字节大小
console.log(this.result, new Blob([this.result]));
//发送文件
postBinary(this.result, url);
};
}
}
//发送二进制文件
function postBinary(binary, url){
let xhr = new XMLHttpRequest();
xhr.open("post", url);
//二进制流数据(如常见的文件下载)
xhr.setRequestHeader("Content-Type", "application/octet-stream");
//针对某些特定版本的mozillar浏览器的BUG进行修正
xhr.overrideMimeType("application/octet-stream");
if(xhr.sendAsBinary){
xhr.sendAsBinary(binary);
}else{
xhr.send(binary);
}
xhr.onreadystatechange = function(evt){
if(xhr.readyState === 4){
if(xhr.status === 200){
console.log("success");
}
}
}
}
若出现405 (Method Not Allowed)
错误,检查下请求头部的Content-Type
信息。注意请求连接URL
,绝大多数Web服务器,都不允许静态文件响应POST
请求。
事件
FileReader
通过异步的方式读取文件内容,返回的结果均时通过事件回调的方式进行获取。
事件 | 描述 |
---|---|
onabort |
当读取操作被中止时触发调用 |
onerror |
当读取操作发生错误时触发调用 |
onload |
当读取操作成功完成时触发调用 |
onloadend |
当读取操作完成时触发调用,不管是否读取成功。 |
onloadstart |
当读取操作将要开始之前触发调用 |
onprogress |
当读取数据过程中周期性调用 |
例如:拖动图片显示
$ vim index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>drag image</title>
<style>
.active{
background:1px firebrick;
}
.drop{
border:1px solid #eee;
width:15rem;
height:15rem;
line-height:15rem;
font-size:5rem;
text-align:center;
color:#eee;
margin: auto;
float:left;
}
</style>
</head>
<body>
<div class="drop" id="drop">+</div>
<div id="show"></div>
<script src="index.js"></script>
</body>
</html>
$ vim index.js
//手动创建Blob URL
const createUrl = URL && URL.createObjectURL || window.webkitURL && window.webkitURL.createObjectURL || window.createObjectURL;
//手动删除Blob URL
const revokeUrl = URL && URL.revokeObjectURL || window.webkitURL && window.webkitURL.revokeObjectURL || window.revokeObjectURL;
const drogEle = document.getElementById("drop");
const showEle = document.getElementById("show");
//在拖动的元素进入到放置目标时执行
drogEle.ondragenter = function (evt) {
//获取通过拖放动作拖动到浏览器的数据
const dt = evt.dataTransfer;
//过滤文件
if (dt.types && dt.types.indexOf("Files") != -1) {
//添加类名
this.classList.add("active");
}
return false;
}
//当被鼠标拖动的对象离开其容器范围内时触发此事件
drogEle.ondragleave = function (evt) {
//移除类名
this.classList.remove("active");
};
//当某被拖动的对象在另一对象容器范围内拖动时触发此事件
drogEle.ondragover = function (evt) {
return false;
}
//在一个拖动过程中释放鼠标键时触发此事件
drogEle.ondrop = function (evt) {
evt.preventDefault();
//获取通过拖放动作拖动到浏览器的数据
const dt = evt.dataTransfer;
let files = dt.files;
let i, len, file, img;
for (i = 0, len = files.length; i < len; i++) {
file = files[i];
if (file.type.indexOf("image/") == -1) return;
//创建图片
img = new Image();
img.style.width = "15rem";
img.src = createUrl(file);
img.onload = function (e) {
showEle.innerHTML = "";
showEle.appendChild(img);
//手动删除Blob URL
revokeUrl(img.src);
}
}
this.classList.remove("active")
return false;
}
// 可以使用FileReader异步对Blob对象的数据进行修改
function read(blob) {
let reader = new FileReader();
// 异步读取
reader.readAsText(blob);
reader.onload = function (evt) {
console.log(reader.result);
}
reader.onerror = function (evt) {
console.log("error happens...")
}
reader.onprogress = function (evt) {
drogEle.innerText = evt.loaded + "/" + evt.total;
}
}
文件 File
H5新特性File API是在表单文件输入字段的基础上,添加了直接访问文件信息的接口。
<input type="file" />
表单文件输入字段input[type=file]
属性
属性 | 描述 |
---|---|
multiple |
是否可选择多个 |
accept |
服务器可接收的文件类型 |
required |
表单提交时是否为必须值 |
accept
服务器可接收的文件类型分为:
类型 | 值 |
---|---|
音频 | audio/* |
视频 | video/* |
图像 | image/* |
常见表单文件控件
<!--调用相机、图片或相册-->
<input type="file" accept="image/*" />
<!--调用相册-->
<input type="file" accept="image/*" multiple />
<!--调用相机-->
<input type="file" accept="image/*;capture=camera" />
<!--调用麦克风-->
<input type="file" accept="audio/*;capture=microphone" />
<input type="file" accept="video/*;capture=camcorder" />
H5在DOM中为文件输入元素input[type=file]
添加了一个FileList
文件集合的files
属性,FileList
对象不能手动构造,只能被动地读取,只有当用户主动触发文件读取行为时,JavaScript才能访问到FileList
对象,而这通常发生在表单选择文件或拖拽文件时。
FileList {0: File(80206), length: 1}
在通过表单form
元素选择一个或多个文件时,FileList
文件集合中将包含一组file
对象,每个file
对象对应一个文件,每个file
对象都具有以下属性:
属性 | 描述 |
---|---|
name |
本地文件系统的文件名称 |
size |
本地文件的字节大小 |
type |
文件的MIME类型 |
例如:
$ vim index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<input type="file" multiple="multiple" accept="image/*" required="true" />
<script src="index.js"></script>
</body>
</html>
$ vim index.js
//从DOM中获取表单文件字段
let fileEle = document.querySelector("input[type=file]");
console.log(fileEle);
//表单文件字段添加change事件监听器
fileEle.addEventListener("change", function(evt){
console.log(evt);
//获取FileList对象
let files = this.files;
console.log(files, files.length);
//判断上传文件个数
if(files.length == 1){
//获取File对象
let file = files[0];
//文件读取器 读取文件
let fileReader = new FileReader();
//以DataURL字符串的形式读取文件
fileReader.readAsDataURL(file);
//当文件上传读取完毕
fileReader.onload = function(evt){
console.log(fileReader.result);
}
}
});
Chrome浏览器重查看File对象的结构
File(101797) {
name: "COSE18061956_987227.jpg",
lastModified: 1542420897667,
lastModifiedDate: Sat Nov 17 2018 10:14:57 GMT+0800 (中国标准时间),
webkitRelativePath: "",
size: 101797,
webkitRelativePath: ""
}
统一资源定位符 URL
window
的URL对象用于生成指定File
对象或Blob
对象的URL
let objectURL= window.URL.createObjectURL(file|blob)
使用二进制数据生成URL,每次执行createObjectURL
方法都会得到不一样的URL。这个URL的生存事件等同于网页的存在时间,一旦网页刷新或卸载,这个URL就会失效。除此之外,也可以手动调用revokeObjectURL
方法使这个URL失效。
window.URL.revokeObjectURL(objectURL)
例如:使用URL对象在页面中插入图片
二进制数组
二进制数组接口于2011年2月分布属于独立规范,ES6将其纳入ECMAScript规范并新增了方法,它以数组的语法形式处理二进制数据,所以称为二进制数组。
二进制数组设计的原始目的与WebGL项目相关,WebGL是浏览器与显卡之间的通信接口,最初为了满足JavaScript与显卡之间大量实时的数据交换,数据通信必须是二进制的,而不能使用传统的文本格式。文本格式传递一个32位整数时,两端的JavaScript脚本与显卡都需要进行格式转换,非常耗时。此时需要存在一种机制 ,可以像C语言那样,直接操作字节,将4字节的32位整数以二进制形式原封不动地送入显卡,这样脚本的性能就会大幅提升。
二进制数组就在这种背景下诞生了,它很像C语言的数组,允许开发者以数组下标的形式直接操作内存,大大增强了JavaScript处理二进制数据的能力,使得开发者有可能通过JavaScript与操作系统的原生接口进行二进制通信。
需要注意的是,二进制数组并不是真正的数组,而是类似数组的对象。浏览器的很多API都会使用二进制数组来操作二进制数据,典型的比如File API、XMLHttpRequest、Fetch API、Canvas、WebSocket等。
二进制数组由三类对象组成,分别是ArrayBuffer对象、TypedArray视图、DataView视图。ArrayBuffer对象、TypedArray视图、DataView视图是JavaScript操作二进制数据的一个接口,统称为二进制数组。
- ArrayBuffer对象 (二进制数组缓冲区)
代表原始的二进制数据,设计用来保存给定字节长度的二进制数据的数据结构。 - TypedArray视图(类型化数组视图)
用来读写简单类型的二进制数据,是操作ArrayBuffer的一种视图,每一项都是相同的大小和类型。 - DataView视图(数据视图)
用来读写复杂类型的二进制数据,是操作ArrayBuffer的另一种视图,不同的是每一项可以有自己的大小和类型。
二进制数组缓冲区 ArrayBuffer
ArrayBuffer主要用来高效快速地访问二进制数据,比如WebGL、Canvas 2D、WebAudio所使用的数据。在这些场景下,通常希望以充分利用硬件性能的方式,或最容易通过网络传输的方式来存储数据。
ArrayBuffer对象代表内存中的一段二进制数据,不能直接进行读写,只能通过视图进行读写操作,视图的作用是以指定格式解析二进制数据,视图部署了数组接口以实现使用数组的方法来操作内存。
ArrayBuffer描述的是内存中缓冲区上的一个数据块,没有格式也无法直接访问,单位是8位的字节。
new ArrayBuffer(length)
ArrayBuffer的构造函数用于创建一个通用且固定长度的二进制数据缓冲区,可以分配一段存放二进制数据的连续内存区域。参数是所需要的内存字节大小,返回值是一个指定大小的ArrayBuffer对象,其内容被初始化为0。
// 生成一段16字节的内存区域,每个字节的值默认都是0。
const buf = new ArrayBuffer(16);
console.log(buf.byteLength);//16
属性 | 描述 |
---|---|
ArrayBuffer.length |
构造函数中的length 属性 |
ArrayBuffer.prototype |
通过ArrayBuffer的原型对象可以为ArrayBuffer对象添加属性 |
get ArrayBuffer[@@species] |
返回ArrayBuffer的构造函数 |
例如:使用AJAX请求二进制数据时需设置数据的响应格式为arraybuffer
let xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "arraybuffer";
xhr.onload = function(evt){
const buf = xhr.response;
console.log(buf);
};
xhr.send();
注意AJAX的响应类型responseType
可选值包括text
、arraybuffer
、blob
、document
,默认为text
。
方法 | 描述 |
---|---|
ArrayBuffer.isView(arg) |
若参数为ArrayBuffer的视图实例则返回true否则返回false |
ArrayBuffer.transfer(oldBuffer[, newByteLength]]) |
返回新的ArrayBuffer对象,内容取自oldBuffer中的数据并根据newByteLength的大小对数据及逆行截取或补零。(实验中) |
ArrayBuffer.slice() |
分片截取,将内容区域部分数据拷贝生成新的ArrayBuffer对象。 |
ArrayBuffer.slice(start, end)
slice()
分片截取其实包含两个步骤:
- 第一步是先分配一段新的内存
- 第二步是将原ArrayBuffer对象拷贝过去
slice()
方法接受两个参数:
- 第一个参数表示拷贝开始的字节序号
- 第二个参数表示拷贝截至的字节序号。
如果省略第二个参数,默认则截取到原ArrayBuffer的结尾。
ArrayBuffer对象除了slice()
方法外不提供任何直接读写内存的方法,只允许在其上方建立视图,然后通过视图读写。
let buf = new ArrayBuffer(32);
let bf = buf.slice(0, 8);
console.log(bf);
视图 View
ArrayBuffer作为内存二进制缓冲区对象,用于表示通用的、固定长度的原始二进制数据缓冲区。
ArrayBuffer作为内存区域,可以存放多种类型的数据,不同的数据拥有不同的存储方式即视图View。
ArrayBuffer对象代表储存二进制数据的一段内存,不能直接读写,只能通过视图(TypedArray和DataView)来读写,视图的作用是以指定格式解读二进制数据。
ArrayBuffer不能直接操作,而是需要通过类型化数组视图TypedArray对象或数据视图DataView对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。
在同一台机器上,访问ArrayBuffer的方式不一样就会得到不同的字节序,换句话说,使用TypedArray和DataView两种方式去读取同一个ArrayBuffer得到的结果会有所不同。
类型化数组视图 TypedArrays
为了达到最大的灵活性和效率,JavaScript类型化数组TypedArrays将实现拆分为缓冲和视图两部分,一个缓冲(由ArrayBuffer对象实现)描述的是一个数据块,缓冲没有格式可言,且不提供机制访问其内容。为了访问在缓冲对象中包含的内容,需要使用视图。视图提供了上下文即数据类型、起始偏移量、元素,将数据转换为实际有类型的数组来使用。
类型化数组TypedArray视图语法上其实就是普通的数组,只不过是针对内存进行操作,而且它的每个成员都具有特定的数据类型。因此类型化数组是建立在ArrayBuffer对象基础上,用于操作一段可以存放数据的连续内存区域。
类型化数组与普通数组相比,普通数组可以存放多种数据类型,类型化数组只能存放0和1的二进制数据。普通数组会将数据保存到堆内存上,类型化数组则会将数据保存到栈内存空间,所以类型化数组读取数据时更加快速。类型化数组初始化后长度固定不可扩容,普通数组则可以自由地增减长度。
数据类型
- 不同的视图类型,所能容纳的数值范围是确定的。超出这个范围,就会出现溢出。
视图类型 | 数据类型 | 有无符号 | 占用字节 | 占有位数 | 数值范围 |
---|---|---|---|---|---|
Int8Array |
Int8 整数 | 有符号 | 1Byte | 8Bit | -128~127 |
Uint8Array |
Uint8 整数 | 无符号 | 1Byte | 8Bit | 0~255 |
Int16Array |
Int16 整数 | 有符号 | 2Byte | 16Bit | - |
Uint16Array |
Uint16 整数 | 无符号 | 2Byte | 16Bit | - |
Int32Array |
Int32 整数 | 有符号 | 4Byte | 32Bit | - |
Uint32Array |
Uint32 整数 | 无符号 | 4Byte | 32Bit | - |
Float32Array |
Float32 浮点数 | - | 4Byte | 32Bit | - |
Float64Array |
Float64 浮点数 | - | 8Byte | 64Bit | - |
实例属性 | 描述 |
---|---|
TypedArray.prototype.buffer |
只读,获取整段内存区域对应的ArrayBuffer缓冲区域对象 |
TypedArray.prototype.length |
成员长度 |
TypedArray.prototype.byteLength |
获取类型化数组占据的内存字节长度 |
TypedArray.prototype.byteOffset |
获取类型化数组从底层ArrayBuffer内存缓冲区的哪个字节开始 |
静态属性 | 描述 |
---|---|
BYTES_PER_ELEMENT |
类型化数组每个元素占用字节数 |
实例方法 | 描述 |
---|---|
TypedArray.prototype.set() |
用于复制类型化数组,也就是将一段内存中的内容完全复制到另一段内存中。 |
TypedArray.prototype.subarray() |
用于从类型化数组中再建立一个新的视图 |
生成方式
类型化数组视图可以通过两种方式生成:
- 第一种是在内存缓冲区上生成视图
- 第二种是直接在内存上分配生成
通过ArrayBuffer生成视图
在ArrayBuffer对象上生成视图时,可以根据不同的数据类型建立多个视图。
//创建8字节长度的类型化数组
let buf = new ArrayBuffer(8);
console.log(buf);
//[[Uint8Array]]: Uint8Array(8) [0, 0, 0, 0, 0, 0, 0, 0]
//[[Int16Array]]: Int16Array(4) [0, 0, 0, 0]
//[[Int32Array]]: Int32Array(2) [0, 0]
//创建一个指向buf的Uint8无符号整型视图,开始字节位置为4,结束字节直到缓冲区末尾。
let uint8view = new Uint8Array(buf, 4);
console.log(uint8view);//Uint8Array(4) [0, 0, 0, 0]
//在类型化数组buf上创建一个Int16有符号整型的视图,开始字节位置为2,长度为2。
let int16view = new Int16Array(buf, 2, 2);//Int16Array(2) [0, 0]
console.log(int16view);
//在类型化数组buf上创建一个指向buf的Int32有符号的整型视图,开始字节位置为0,直到缓冲区的末尾。
let int32view = new Int32Array(buf);
console.log(int32view);//Int32Array(2) [0, 0]
通过直接分配内存生成视图
//生成一个拥有8个成员的Float64Array的类型化数组共计64字节
let f64a = new Float64Array(8);//视图构造函数的参数是成员的个数
//依次对每个成员进行赋值
f64a[0] = 10;
f64a[1] = 20;
f64a[2] = f64a[1] + f64a[0];
f64a[3] = f64a[2] + f64a[1];
console.log(f64a);//Float64Array(8) [10, 20, 30, 50, 0, 0, 0, 0]
分配前
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
---|
分配后
10 | 20 | 30 | 50 | 0 | 0 | 0 | 0 |
---|
//生成8字节的内存缓冲区
const buf = new ArrayBuffer(8);
console.log(buf);//ArrayBuffer(8) {}
//从内存缓冲区buf的第二个字节开始截取三个字节用来存放无符号整数
let u8a = new Uint8Array(buf, 2, 3);
console.log(u8a);//Uint8Array(2) [0, 0, 0]
//数据类型所占据的字节数
console.log(u8a.BYTES_PER_ELEMENT);//1
//获取类型化数组所在的内存缓冲区
console.log(u8a.buffer);//ArrayBuffer(8) {}
//获取类型化数组的成员长度
console.log(u8a.length);//3
//获取类型化数组的字节长度
console.log(u8a.byteLength);//3
//获取类型化数组的字节偏移量
console.log(u8a.byteOffset);//2
字节序号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
值 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
字节序号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
值 | 0 | Uint8 | Uint8 | Uint8 | 0 | 0 | 0 | 0 |
TypedArray.prototype.set()
set()
方法是整段内存的复制,因此比一个个拷贝成员的方式要快的多。
比如dst.set(src)
表示将源类型数组src
的内容复制到目标类型化数组dst
//直接在内存中创建8个字节长度的空间,用于存储无符号8位整数。
let u8a_1 = new Uint8Array(8);
u8a_1[0] = 10;
u8a_1[1] = 20;
u8a_1[2] = 30;
console.log(u8a_1);//Uint8Array(8) [10, 20, 30, 0, 0, 0, 0, 0]
//直接在内存中创建4个字节长度的空间,用于存储无符号8位整数。
let u8a_2 = new Uint8Array(4);
u8a_2[0] = 100;
u8a_2[1] = 101;
u8a_2[2] = 200;
u8a_2[3] = 255;//无符号8位整数的最大长度为256,取值范围是0到255
console.log(u8a_2);//Uint8Array(4) [100, 101, 200, 255]
//将u8a_2的内容完全复制给u8a_1
u8a_1.set(u8a_2);
console.log(u8a_1);//Uint8Array(8) [100, 101, 200, 255, 0, 0, 0, 0]
console.log(u8a_2);//Uint8Array(4) [100, 101, 200, 255]
dst.set(src, index)
方法可以接受第二个参数index
表示从将源类型化数组的内容完全复制到目标类型类型化数组dst
中,由目标类型化数组dst
中索引为index
的成员即第index+1
个位置的成员开始。
//直接在内存中创建8个字节长度的空间,用于存储无符号8位整数。
let u8a_1 = new Uint8Array(8);
u8a_1[0] = 10;
u8a_1[1] = 20;
u8a_1[2] = 30;
console.log(u8a_1);//Uint8Array(8) [10, 20, 30, 0, 0, 0, 0, 0]
//直接在内存中创建4个字节长度的空间,用于存储无符号8位整数。
let u8a_2 = new Uint8Array(4);
u8a_2[0] = 100;
u8a_2[1] = 101;
u8a_2[2] = 200;
u8a_2[3] = 255;//无符号8位整数的最大长度为256,取值范围是0到255
console.log(u8a_2);//Uint8Array(4) [100, 101, 200, 255]
//将u8a_2内容复制到u8a_1中,由u8a_1的索引为三的第四个成员开始。
u8a_1.set(u8a_2, 3);
console.log(u8a_1);//Uint8Array(8) [10, 20, 30, 100, 101, 200, 255, 0]
console.log(u8a_2);//Uint8Array(4) [100, 101, 200, 255]
let u8a_1 = new Uint8Array(8);
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|
10 | 20 | 30 | 0 | 0 | 0 | 0 | 0 |
let u8a_2 = new Uint8Array(4);
1 | 2 | 3 | 4 |
---|---|---|---|
100 | 101 | 255 | 0 |
u8a_1.set(u8a_2, 3)
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|
10 | 20 | 30 | 100 | 101 | 200 | 255 | 0 |
数据视图 DataView
为了解决各种硬件设备、数据传输等对默认字节序(Endian)的设定不一,而导致解码时发生的混乱问题,JavaScript提供了数据视图DataView让开发者在对内存进行读写时,可以手动设定字节序的类型。
DataView数据视图是一个可以从ArrayBuffer对象中读写多种数值类型的底层接口,使用它时不用考虑不同平台的字节序问题。