H5 Binary

文件操作一直是早期浏览器的痛点,全封闭式的不给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"}

Blobslice()方法截取的是文件数据二进制字符,也相当于数据,因为二进制字符表示的就是数据本身。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属性或backgroundurl属性可以通过赋值为图片网络地址或Base64的方式来显示图片。文件上传中,一般会先将本地文件上传到服务器,上传成功后由服务器返回图片的网络地址再到前端显示。通过FileReaderreadAsDataURL方法可以不经过后台直接将本地图片显示在页面上。这样做可以减少前后端频繁的交互过程以减少服务器无用的图片资源。

$ 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标签由后端拉取二进制内容。

操作步骤

  1. 通过input[type=file]标签获取本地文件对象File
  2. 通过FileReaderreadAsArrayBuffer方法将File对象转换为ArrayBuffer数组缓冲区对象
  3. 创建AJAXXHR对象并配置请求参数
  4. 通过XHR对象的sendAsBinary方法将文件的ArrayBuffer内容填充至POSTbody体后向服务器发送
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
属性 描述
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可选值包括textarraybufferblobdocument,默认为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对象中读写多种数值类型的底层接口,使用它时不用考虑不同平台的字节序问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容