扒一扒 JavaScript 中不常见的一些 object

1,451 阅读8分钟

前言

今天仔细阅读了MDN的这篇guide:Using XMLHttpRequest,在里面发现了好多以前听都没听过的概念,尤其是那些不为人知的Object,所以在这里特地把它们拎出来讲一下,不求多深入,只想粗略了解一下。

1、ArrayBuffer

为什么需要这么一种数据类型呢?众所周知,Array对象可以动态变化长度并且可以包含任意类型的JS值。JS引擎对其优化以便可以让这些数组执行起来很快。但是随着web应用变得越来越强大,添加一些诸如音视频的操作特性,使用WebSockets访问原始数据等等和底层有关的行为时,就会发现如果JS能够直接快速且简单地操作原始数据那是相当有用的,于是就产生了ArrayBuffer这样的数据类型。

ArrayBuffer对象用于表示一个通用的固定长度原始二进制数据缓存。你不能直接操作ArrayBuffer的内容。但是你可以使用特定的格式来创建typed array objectsDataView对象以表示这种类型的buffer,然后就可以用来读或者写缓存的内容。那么什么是typed array objectsDataView

1.1、DataView

DataView 视图提供一个底层接口来读取和写入多种数据类型到一个ArrayBuffer中去而忽略平台的大小端。

语法

new DataView(buffer [, byteOffset [, byteLength]])
  1. buffer:一个已存在的ArrayBuffer,用来存储新的DataView对象的;
  2. byteOffset:以字节为单位,新视图存放在相对于指定buffer的第一个字节的偏移。如果没有指定,那么buffer的视图将会从第一个字节开始。
  3. byteLength:字节数组的长度。如果没有指定,视图的长度将会匹配buffer的长度。

该对象提供了众多的方法来读写整形8/16/32比特的有符号整数或者无符号整数以及32/64比特的浮点数,比如:

var buffer = new ArrayBuffer(16); // 创建一个16字节的ArrayBuffer
// 创建一个DataView的对象,放在上面创建的buffer中,偏移为0,
// 也就是在上述buffer中的头部
var dv = new DataView(buffer, 0); 

// 以小端形式写入一个有符号的16比特的整数到视图中去
dv.setInt16(0, 42, true);
// 以大端形式读取
dv.setInt16(0, 42);
// 以小端形式读取
dv.setInt16(0, 42, true);

大家知道第一个读取的结果和第二个结果是什么吗?答案是:1075242。原因其实很简单的:

42的二进制是:00000000 00101010,如果是小端形式写入buffer的话,那么假设写入到的内存地址是0x10000000(32bit的系统):

内存地址:低-------------------------------------高

0x10000000: 00101010 00000000 00000000 00000000

因为是低字节写在低地址,高字节写在高地址,所以有上面的存储方式。

然后我们以大端的模式读取的时候,是因为低字节写在高地址,高字节写在低地址,所以读取出来的结果便是: 00101010 00000000,所以结果便是10752。

然后小端模式是相反的,所以仍然是42。因为这种差别,所以在MDN上给出了使用这种方式来判断你使用的系统的字节序:

var littleEndian = (function() {
  var buffer = new ArrayBuffer(2);
  new DataView(buffer).setInt16(0, 256, true /* littleEndian */);
  // Int16Array uses the platform's endianness.
  return new Int16Array(buffer)[0] === 256;
})();
console.log(littleEndian); // true or false

因为Int16Array使用的平台的字节序,所以事先假设为小端模式存储,然后读取出来一对比就知道当前平台使用的字节序了,这种方法值得记住!!

1.2、TypedArray

TypedArray对象描述了一个类数组的底层二进制数据buffer的视图。全局变量中没有TypedArray的属性也没有一个可以直接访问的TypedArray构造体。不过全局变量中有一组不同的属性,它们的值是有指定元素类型的typed array结构体,

ES6定义了一个TypedArray构造体作为所有TypedArray构造体的[[Prototype]]。这个构造体并没有直接暴露出去:也就是说没有全局变量%TypedArray%或者TypedArray属性。它只能通过Object.getPrototypeOf(Int8Array)或者类似的属性(Int8Array可以改为下面列举的任意一个函数)直接访问。所有的TypedArray构造体从%TypedArray%构造体函数继承了公共的属性。另外所有的typed array原型(TypedArray.prototype)都将%TypedArray%.prototype作为它们的[[Prototype]]。

比如:

var dv = new Int8Array(4)

dv的结构可以在控制台上看到:

%TypedArray%构造体并不是特别有用,调用它或者在一个新的表达式中使用它将会抛出一个TypeError的错误,除了当你在支持子类的JS引擎中对象的创建。目前没有这样的引擎,所以%TypedArray%只对polyfill函数或者所有TypedArray构造体上的属性有用。

当创建一个TypedArray实例(也就是Int8Array或者类似函数的实例),将在内存中创建一个数组缓存(如果ArrayBuffer对象作为构造体参数那么就使用这个array buffer),该缓存的地址作为该实例的内部属性保存起来,并且%TypedArray%.prototype的所有方法使用该缓存地址进行各种操作。

全部的具体对象如下所示:

new TypedArray(length);
new TypedArray(typedArray);
new TypedArray(object);
new TypedArray(buffer [, byteOffset [, length]]);

where TypedArray() is one of:

Int8Array();
Uint8Array();
Uint8ClampedArray();
Int16Array();
Uint16Array();
Int32Array();
Uint32Array();
Float32Array();
Float64Array();

上面新建的对象都可以使用标准的数组索引语法去访问数组的每个元素。但是在typed arrays上获取或设置索引的属性是不会去搜索该属性上的原型链的,即使当索引已经溢出。索引的属性将会从ArrayBuffer中查找而不会去查找对象的属性。你仍然可以使用命名的属性。

比如:

Int8Array.prototype.foo = "bar";
var dv = new Int8Array(4);

那么现在新建的任何Int8Array的原型都将会有这个一个foo的属性:

具体的范例参考MDN上提供的:TypedArray

1.3、实例操作

基本上ArrayBuffer用于存储二进制数据,比如一张图片,然后你就可以在这张图片的基础上做各种操作比如添加alpha做成有RBGA形式的新图片。在MDN的JavaScript typed arrays中大体解释了其适用的几个场景。

更多实用的例子可以参考:Sending and Receiving Binary Data

2、Blob对象

一个Blob对象代表的是一种类似于文件的不可变的原始数据,也即是说blob对象表示一堆用来呈现文件数据字节,但它并不能索引到真正的文件。Blobs表示的数据在原生的JS格式中是不必要的。blob对象和文件一样有自己的长度以及MIME类型。Js的File接口就是基于Blob,它继承了Blob的所有功能并扩展以能够支持用户系统中的文件。

Blob的大部分API都是异步的,但是也有同步的API可用所以它们可以在Web Workers中使用。

blob对象的内容可以作为ArrayBuffer来读取。

比如,先创建一个blob对象:

var myBlob = new Blob(["This is my testing of blob"], {type : "text/plain"});

然后我们使用FileReader来读取内容:

var myReader = new FileReader();
myReader.addEventListener("loadend", function(e){
    console.log(e.srcElement.result);//prints a string
});
//start the reading process.
myReader.readAsText(myBlob);

Blob对象的数据也可以通过使用URL的形式(blob:// URLs)获取,这个时候使用createObjectURL来获取,比如:

//cross browser

window.URL = window.URL || window.webkitURL;

var blob = new Blob(['body { background-color: yellow; }'], {type: 'text/css'});

var link = document.createElement('link');
link.rel = 'stylesheet';
//createObjectURL returns a blob URL as a string.
link.href = window.URL.createObjectURL(blob);
document.body.appendChild(link);

除此之外还可以使用AJAX将得到的一些二进制流存储起来:

var xhr = new XMLHttpRequest(); 
xhr.open("GET", "/favicon.png"); 
xhr.responseType = "blob";//force the HTTP response, response-type header to be blob
xhr.onload = function() 
{
    document.getElementsByTagName("body")[0].innerHTML = xhr.response;//xhr.response is now a blob object
}
xhr.send();

接着我们可以在ArrayBuffer中获取blob内容然后分析,这个过程可以调用FileReader.readAsArrayBuffer()来完成。

var xhr = new XMLHttpRequest(); 
xhr.open("GET", "/favicon.png"); 
//although we can get the remote data directly into an arraybuffer using the string "arraybuffer" assigned to responseType property. For the sake of example we are putting it into a blob and then copying the blob data into an arraybuffer.
xhr.responseType = "blob";

function analyze_data(blob)
{
    var myReader = new FileReader();
    myReader.readAsArrayBuffer(blob)

    myReader.addEventListener("loadend", function(e)
    {
        var buffer = e.srcElement.result;//arraybuffer object
    });
}

xhr.onload = function() 
{
    analyze_data(xhr.response);
}
xhr.send();

3、File对象

一个File对象指的是本地文件系统中的一个实际的文件。在上面我们说过File对象继承了Blob类的所有属性和方法。所以说虽然二者是不同的,但是却暴露了相同的方法和属性。

没有特定的方法去创建一个File对象,但是一些JS的API会返回对File对象的引用。File对象可以从一组用户选择的文件列表中返回的FileList对象获取也可以从一个拖拽操作的 DataTransfer对象中获取:

var element = document.getElementsByTagName("body")[0];

//files is a filelist
function fileselected(files) 
{
    for(var i = 0; i < files.length; i++) 
    {
        var f = files[i];
        element.innerHTML = element.innerHTML + f.name + " " + f.size + " " + f.type; 
    }
}

4、Plain对象

这个概念不是JS原生的,而是Jquery提供的。根据Jquery的官方文档解释:

PlainObject类型是一种包含零个或者多个键值对的对象。这种纯粹的对象换句话说是一个`Object`对象。它在Jquery中被设计成“纯粹”是为了和JS其他的内建对象做区别:比如`null`,用户定义的数组,以及主机内建的对象`document`等等。

PlainObject的创建应该是使用{}或者new Object.

注意: Host对象(或浏览器宿主环境中所使用的对象,用来完成的ECMAScript执行环境)在检测跨平台时存在很多的不一致,难以提供跨平台的强劲的检测函数。在某些情况下,$.isPlainObject()的结果可能在不同的浏览器评估不一致。

参考:

  1. developer.mozilla.org/en-US/docs/…
  2. developer.mozilla.org/en-US/docs/…
  3. developer.mozilla.org/en-US/docs/…
  4. api.jquery.com/jquery.ispl…
  5. api.jquery.com/Types/