JavaScript ES6 新增特性整理 - 3.新增的对象

 2016年10月06日    207     声明


ES6 语言标准中扩展很多新对象,如:将用于异步处理的Promise规范纳入语言标准,做为原生对象提供;增加了MapSet对象及其weak版本;Symbol对象可以用来创建独一无二的标识符,还可以访问 ES5 中没有暴露给开发者的符号。

  1. 类型数组
  2. Map对象
  3. Set对象
  4. Promise对象
  5. Symbol类型
  6. Proxy对象
  7. Reflect对象

1. 类型数组

类型数组(Typed Arrays)用于赋予了JavaScript处理二进制数据的能力,类型数组并不是单一的对象,而是对一系列对象的统称。二进制数组由以下三类对象组成:

  • ArrayBuffer是一个是一个用于表示二进制缓冲区(buffer)的对象,它并没有能力去访问和操作它自身的数据内容,要操作ArrayBuffer中的数据,需要创建一个Typed Array ViewDataView来读写缓存区内容。
  • Typed Array View(Typed Array Object)即类型数组视图对象,是一组具有描述性的名字的不同类型的数组视图对象。如:Uint8ArrayInt8ArrayInt32ArrayFloat32Array
  • DataView提供了一些底层的API,通过这些API可以从ArrayBuffer中读/写数据。

1.1 ArrayBuffer对象

ArrayBuffer(缓冲数组)是一段二进制的内存空间,它用于呈现通用、固定长度的二进制数据的类型。它不能直接构造并填充其内容,而应该先创建一个DataView或是通过TypedArray来读写具体类型的二进制数组内容。

如,创建一个ArrayBuffer并使用Typed Array ViewDataView读写数据:

var buffer = new ArrayBuffer(8);

// 使用 Typed Array View 读写
var int32 = new Int32Array(buffer);

int32[0] = 42;
console.log(int32[0]); // 42

// 使用 DataView 读写
var dv = new DataView(buffer, 0);
dv.setInt16(1, 42);
dv.getInt16(1);

Typed Array View也可以独立使用:

// 创建一个指定长度的 TypedArray
var uint8 = new Uint8Array(2);
uint8[0] = 42;
console.log(uint8[0]); // 42


1.2 TypedArray视图对象

TypedArray对象是一个描述二进制缓存数据(ArrayBuffer)的类似数组的视图对象。TypedArray并不是一个单独的对象,而是由一组子对象构成,我们可以根据缓存区内容类型的不同而建立不同类型的数据视图,每种类型的视图都可以从不同的索引位置开始为缓存建立内容视图。

我们可以像下面这样构造一个TypedArray对象:

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

构造参数说明:

  • length - 表示创建一个长度为length的类型数组
  • typedArray - 从类型数组typedArray创建一个新的类型数组,原类型数组中的每个值都会被转化为和构造器到对应的新数组里。
  • object - 当传入一个object时,相当于使用TypedArray.from()方法创建一个新的类型数组。
  • buffer [, byteOffset [, length]] - 从一个指定的buffer和可选参数byteOffsetlength创建一个类型数组

TypedArray可以是以下对象的构造的函数之一:

  • Int8Array:8位有符号整数,长度1个字节。
  • Uint8Array:8位无符号整数,长度1个字节。
  • Uint8ClampedArray:8位无符号整数,长度1个字节,溢出处理不同。
  • Int16Array:16位有符号整数,长度2字节。
  • Uint16Array:16位无符号整数,长度2字节。
  • Int32Array:32位有符号整数,长度4字节。
  • Uint32Array:32位无符号整数,长度4字节。
  • Float32Array:32位浮点数,长度4字节。
  • Float64Array:64位浮点数,长度8字节。


1.3 DataView对象

DataView提供了一个用于读写ArrayBuffer的,更加底层的访问接口。通过该对象提供的接口,我们可以读写多个ArrayBuffer,而且这些访问接口是与平台无关的格式类型。

可以像下面这样创建DataView对象:

new DataView(buffer [, byteOffset [, byteLength]])

其中:

  • buffer - 表示已存在的一个ArrayBuffer
  • byteOffset - 可选,表示偏移量(默认为0)
  • byteLength - 可选,表示读取数据的长度,默认为缓存的数据总数
var buffer = new ArrayBuffer(16);
var dv = new DataView(buffer, 0);

dv.setInt16(1, 42);
dv.getInt16(1); //42


2. Map对象

Map对象是一个简单的键/值映射。其键和值都可以是任意值(对象或原始值)。

我们可以像下面这样创建一个Map对象:

new Map([iterable])
  • iterable - 可选参数,一个可迭代对象,是一个包含 键-值 对的数组,数组中所有的键-值对集合对会被添加到新的Map集合中。

迭代Map对象中的元素时,for…of循环会返回每一个迭代对象的[key, value]数组。

2.1 ObjectMap的比较

ObjectMap都是按存取的结构类型,二者有相似之处。但,二者也有以下几点区别:

  • 对象通常都有自己的原型,也就是说一个对象总有一个"prototype"键。不过现在你可以使用,map = Object.create(null)来创建一个没有原型的对象。
  • 一个对象的键只能是StringSymbol,而Map的键可以是任意值
  • Map的键值对个数很容易获取,而获取一个Object键值对个数确相对复杂。

Map的键可以是任意值,我们可以像下面这样使用Map对象:

var myMap = new Map();

var keyString = "a string",
    keyObj = {},
    keyFunc = function () {};

// 设置各种类型的key
myMap.set(keyString, "String类型key的值");
myMap.set(keyObj, "Object类型key的值");
myMap.set(keyFunc, "Function类型key的值");

myMap.size; // 3

// 获取值
myMap.get(keyString);
myMap.get(keyObj);
myMap.get(keyFunc);

myMap.get("a string");   // "String类型key的值"
					     // 因为 keyString === 'a string'
myMap.get({});           // undefined, 因为 keyObj !== {}
myMap.get(function() {}) // undefined, 因为 keyFunc !== function () {}

Map对象可以使用for…of语句进行迭代:

var myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
for (var [key, value] of myMap) {
  console.log(key + " = " + value);
}
// 0 = zero
// 1 = one
for (var key of myMap.keys()) {
  console.log(key);
}
// 0
// 1

for (var value of myMap.values()) {
  console.log(value);
}
// zero
// one

for (var [key, value] of myMap.entries()) {
  console.log(key + " = " + value);
}
// 0 = zero
// 1 = one


2.2 WeakMap

WeakMap是一种键/值对的集合类型,与Map不同,其只能是对象类型,而可以是任意类型。WeakMap的键是“弱引用”的,这意味着,如果没有其它引用和该键引用同一个对象,该对象将会被当作垃圾回收。

可以像下面这样创建一个WeakMap对象:

new WeakMap([iterable])

其中:

  • Iterable是一个2元数组或者可遍历的且其元素是键/值对的对象。每个键/值对会被添加新的WeakMap中。

WeakMap的一些使用示例:

var wm1 = new WeakMap(),
    wm2 = new WeakMap(),
    wm3 = new WeakMap();
var o1 = {},
    o2 = function(){},
    o3 = window;

wm1.set(o1, 37);
wm1.set(o2, "itbilu.com");
wm2.set(o1, o2); // value可以是任意值,包括一个对象
wm2.set(o3, undefined);
wm2.set(wm1, wm2); // 键和值可以是任意对象,甚至另外一个WeakMap对象
wm1.get(o2); // "itbilu.com"
wm2.get(o2); // undefined,wm2中没有o2这个键
wm2.get(o3); // undefined,值就是undefined

wm1.has(o2); // true
wm2.has(o2); // false
wm2.has(o3); // true (即使值是undefined)

wm3.set(o1, 37);
wm3.get(o1); // 37
wm3.clear();
wm3.get(o1); // undefined,wm3已被清空
wm1.has(o1);   // true
wm1.delete(o1);
wm1.has(o1);   // false


3. Set对象

3.1 Set的构造

Set也是 ES6 中新增的一集合类型,该类型类似于数组,但要求集合中的成员值都是唯一的,其存储的值可以是任意类型,可以是原始值或对象引用。

可以像下面这样构造一个Set对象:

new Set([iterable]);

Set对象的使用:

var mySet = new Set();

mySet.add(1);
mySet.add(5);
mySet.add("ITbilu.com");
var o = {a: 1, b: 2};
mySet.add(o);

mySet.has(1); // true
mySet.has(3); // false, 3没有添加到集合中
mySet.has(5);              // true
mySet.has(Math.sqrt(25));  // true
mySet.has("ITbilu.com".toLowerCase()); // true
mySet.has(o); // true

mySet.size; // 4

mySet.delete(5); 
mySet.has(5);    // false, 5 已经被移除

mySet.size; // 3


3.2 WeakSet

Set类型同样存在一个Weak版本,WeakSet对象是一个无序集合,可以用它来存储任意的对象值,并且对这些对象值保持弱引用关系。

WeakSetSet类型一样,同样用于存储对象值的,并且其中的每个对象值都只能出现一次。但两者也有以下两点不同:

  • WeakSet 对象中只能存放对象值, 不能存放原始值,而 Set 对象都可以。
  • WeakSet 对象中存储的对象值都是被弱引用的,如果没有其他的变量或属性引用这个对象值,这个对象值会被当成垃圾回收。也因此,WeakSet对象是无法被枚举的,也就没有办法拿到它包含的所有元素。

WeakSet对象的使用:

var ws = new WeakSet();
var obj = {};
var foo = {};

ws.add(window);
ws.add(obj);

ws.has(window); // true
ws.has(foo);    // false, 对象 foo 并没有被添加进 ws 中 

ws.delete(window); // 从集合中删除 window 对象
ws.has(window);    // false, window 对象已经被删除了

ws.clear(); // 清空整个 WeakSet 对象


4. Promise对象

4.1 Promise/A+规范

ES6 中新增的用于异步处理的Promise对象,该对象来源于Promise/A+规范。在Promise/A+规范中规定:

  • 一个promise有一到三种状态:pending(等待)、fulfilled或resolved(成功-已完成)、rejected(失败-已拒绝)
  • 一个promise的状态只可能从等待完成或者拒绝状态,不能逆向转换,同时完成拒绝状态不能相互转换
  • promise实例必须实现then方法,而且then必须返回一个promise,同一个promisethen可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致
  • then方法接受两个参数:第一个参数是成功时的回调,在promise等待养成转换到完成态时调用。另一个是失败时的回调,在promise等待状态转换到拒绝状态时调用。同时,then可以接受另一个promise传入,也接受一个类似then的对象或方法,即:thenable对象

Promise


4.2 Promise对象

ES6 将Promise对象纳入语言标准,并做为语言标准的原生对象提供。在 ES6 语言标准中,Promise包括三部分:构造函数、实例方法、静态方法。

构造函数

构造函数用于创建一个Promise,结构如下:

new Promise( /* executor */ function(resolve, reject) { ... } );

创建Promise对象时,需要向构造函数传入一个executor参数,它是一个执行函数,会在创建Promise对象的时候立即执行。这个函数通常被用来执行一些异步操作,包含以下两个参数:

  • resolve - 执行成功时的回调函数,操作完成以后通过这个函数来触发promise的成功状态
  • reject - 执行失败时的回调函数,操作完成以后通过这个函数来触发promise的失败状态
var promise = new Promise(function(resolve, reject) {
  // 异步处理回调函数
  // 处理结束后调用resolve 或 reject方法
  if (操作成功){
    resolve(value);
  } else {
    reject(error);
  }
});


4.3 Promise对象方法

对象方法即静态方法,也就是可以通过Promise调用的方法。Promise包含以下对象方法:

  • Promise.all(iterable) - 该方法会返回一个新的promise对象。iterable是一个可以包含多个promise对象的可迭代对象,iterable对象中的所有promise对象执行成功后会触发成功状态;而其中的任何一个对象执行失败后,会触发失败状态,并将第一个触发失败的promise对象的错误信息作为它的失败错误信息。

    Promise.all()方法通常用于处理多个promise对象集合。

  • Promise.race(iterable) - 当iterable中任意一个子promise执行成功或失败后,父promise也会返回该promise对象的成功或失败信息,并调用父promise相应的处理函数。
  • Promise.reject(reason) - 调用父promise对象失败状态的处理函数,并返回指定失败原因reason
  • Promise.resolve(value) - 调用父promise对象成功状态的处理函数,并返回指定值value

如,Promise.all的使用:

var p1 = Promise.resolve(1),
  p2 = Promise.resolve(2),
  p3 = Promise.resolve(3);
Promise.all([p1, p2, p3]).then(function (results) {
  console.log(results);  // [1, 2, 3]
});


4.4 Promise实例方法

创建Promise实例后,可以通过实例方法为实例添加在resolve(成功) / reject(失败)时调用的回调函数:

  • Promise.prototype.then(onFulfilled, onRejected) - .then方法可以用于设置resolve(成功) / reject(失败)两种状态时的回调函数,两处回调方法都是可选的。其中:
    • resolve(成功)时,onFulfilled方法会被调用
    • reject(失败)时,onRejected方法会被调用
  • Promise.prototype.catch(onRejected) - 该方法用于设置reject(失败)时被调用的回调函数,其相当于promise.then(undefined, onRejected)

如,使用promise.then定义成功和失败两种回调:

promise.then(function(result) {
  // 操作成功
}, function(err) {
  // 操作失败
});

使用promise.thenpromise.catch分别定义:

promise.then(function(result) {
  // 操作成功
}).catch(function(err) {
  // 操作失败
});

更多关于Promise的参考:


5. Symbol类型

Symbol(符号)是一种特殊的、不可变的数据类型,可以作为对象属性的标识符使用。在ES6 之前,对象中的方法和属性的键都是字符串形式,如果有同名的方法或属性就会被覆盖。使用Symbol可以解决这一问题,Symbol创建的标识符都是独一无二的。

可以像下面这样,使用Symbol()函数来创建一个符号:

Symbol([description])

号对象是一个对的符号原始数据类型的隐式对象包装器。其参数description是一个可选的字符串描述,这个描述值一般用于调试而不是访问符号本身。

5.1 关于Symbol类型

符号对象是JavaScript中继:BooleanNullUndefinedStringNumberObject之后,第七种数据类型。创建一个符号类型使用Symbol()函数:

var sym = Symbol();
console.log(typeof sym);  //symbol

创建一个新的原始符号,可以使用Symbol()和一个可选字符串参数作为它的描述:

var sym1 = Symbol();
var sym2 = Symbol("foo");
var sym3 = Symbol("foo");

上面的代码创建三个新的符号。注意,符号类型的描述'foo'唯一的作用就是在调试时,我们可以更好的区分所创始的符号。即使描述值一样,也不是同一个符号,符号类型每次都会创建一个新的符号:

console.log(Symbol("foo") === Symbol("foo"));  //false
console.log(Symbol("itbilu.com") == Symbol("itbilu.com"));  //false


5.2 Symbol对象的使用

每次创建的Symbol值都是唯一的,利用这一特性,可以将其做为标识符使用。将其用于对象属性名时,可以保证对象每一个属性名都是唯一的,不会发生对象属性被覆盖的情况。

使用Symbol值做为对象属性时,不能使用.操作来添加属性:

var sym = Symbol();
var a = {};

a.sym = 'itbilu.com';
a[sym] // undefined
a['sym'] // "itbilu.com"  以点的形式添加属性名其本质上还是一个字符串

可以使用以下三种方式添加符号的对象属性:

var sym = Symbol();

// 1. 用方括号添回
var a = {};
a[sym] = 'itbilu.com';

// 2. 在对象内部定义
var a = {
  [sym]: 'itbilu.com'
};

// 3. 用defineProperty添加
var a = {};
Object.defineProperty(a, sym, { value: 'itbilu.com' });


6. Proxy对象

Proxy(代理)对象用于自定义JavaScript语言的形为(如:属性查找、赋值、枚举、函数调用等),通过该对象使我们有了对JavaScript语言层面修改的能力。

创建一个Proxy语法如下:

var p = new Proxy(target, handler);

其中:

  • target-被代理的一个目标对象(可以是任何类型的对象、数组、函数,甚至是另一个代理)
  • handler-处理对象,其属性是定义代理时的行为函数,在目标对象上执行的代理操作都会被些函数处理
  • 返回值-代理对象实例

Proxy即“代理”,我们通过new Proxy()构造函数,来对target设置处理对象handler。初始化后会返回一个代理实例对象p,该对象是target的代理器,访问target对象的形为都会首先经过该代理器处理。代理器会判断这种形为有没有在handler中定义,如果已定义则由代理形为处理,如果未定义则由target处理。

6.1 术语

在使用Proxy时,应该理解以下术语:

  • traps-提供访问的属性,与操作系统中的trap概念类似
  • target-被代理虚拟化的对象,这个对象常常用作代理的存储后端。
  • handler-包含traps的占位符对象


6.2 Proxy的使用

可以通过Proxy重写对象的get访问器,当对象属性不存在时返回33

var handler = {
  get: function(target, name){
    return name in target?
      target[name]:33;
  }
};

var p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;

console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 33

还可以定义一个 JavaScript 对象,代理会将所有应用到它的操作转发到这个对象上:

var target = {};
var p = new Proxy(target, {});

p.a = 37; // 被转发到代理的操作

console.log(target.a); // 37. 操作已经被正确地转发


7. Reflect对象

Reflect对象通常与Proxy对象一块使用,Proxy通过一些陷入指令来修改对象的默认形为;而Reflect提供了一些静态方法,这些方法与Proxy对象处理器中的方法一一对应,用于获取对象的默认形为、操作对象属性等。

Reflect 对象提供了 14 个静态方法,它们的名字与Proxy听代理方法handler中的名字相同,其中有几个方法在Object对象上也存在同名方法,虽然它们功能类似,但也存在细微差异。

Reflect对象的详细介绍请参考:JavaScript ES6 Reflect对象