ES6新增了两种基本的原生数据集合:Set
和Map
(加上Array
和Object
如今共有四种),以及由二者衍生出的弱引用集合:WeakSet
和WeakMap
。从某个不无狭隘的角度看(不无狭隘?到底有多狭隘多不狭隘呢?),Set
更为相似Array
集合的某种提高,而Map
则为Object
集合的加强,虽然两类在本质上就不相同。html
其自己是生成Set
实例(数据集合)的构造函数,能够接受一个数组(或具备iterable
接口的数据结构)做为参数用来初始化。存储的结构相似数组,不过成员的值在集合中都是惟一的,不会出现重复值。实际的存储顺序(也是遍历顺序)与插入顺序一致,行为的结果和数组相同。segmentfault
其数据结构中没有键名,但为了和Map
统一,也可认为键名和健值是同一值(会在遍历小节中介绍)。内部使用的相等判断规则,除了认为NaN
等于NaN
,与全等于一致。利用Set
中没有重复值的特性,能够简单的实现数组去重。最后一点,其实例的字符串标签为[Object Set]
——厉害啦,都自立了门户。数组
let set = new Set([1, 2]); console.log(...set); // 1 2 console.log({}.toString.call(set)); // [Object Set] let o = {}; [...new Set([1, 2, 2])]; // [1, 2] [...new Set([1, NaN, NaN])]; // [1, NaN] [...new Set([1, o, o])]; // [1, o] [...new Set([1, {}, {}])]; // [1, {}, {}] F(1, {}, 1); // [1, {}],可解析带遍历器的类数组对象。 function F() { console.log([...new Set(arguments)]); } let o = { length: 0 }; [...new Set(o)]; // 报错,不能自动解析不带遍历器接口的类数组对象。 // 做为简单的删除数组重复值的方法。 removeDuplicateValues([1, 2, 2, 3]); // [1, 2, 3] function removeDuplicateValues(arr) { return [...new Set(arr)]; }
关于相等的小知识
两值是否相等通常指的是是否全等,里面有两个比较特殊的例子:NaN & NaN
和-0 & +0
。在全等中,NaN
不等于NaN
,-0
等于+0
都为零。但这两种认定在某些场合中不太接地气,为此ES6给出用于判断两值是否相等方法Object.is()
中,认定NaN
等于NaN
,-0
不等于+0
。数据结构
0除以1为正零,0除以负1为负零,二者在生成上方式上看的确不该该相等。app
非数NaN
是一个不是数字的数字(类型依旧为数字型),没有具体的值,所以两个NaN
是不相等的。不过在用NaN
做为映射中的键时,它应该代指这一类型而不是具体的个体。否者我先设置代码NaN
指向貂蝉,再设置NaN
指代西施,晚上宽衣解带后发现仆人竟将两人同时安置在被窝之中,笑盈盈水灵灵的。这,让我如何是好!dom
其自己是生成Map
实例(数据集合)的构造函数,能够接受一个包含键值对的数组(或具备iterable
接口的数据结构)做为参数用来初始化。简单的说,键值对是包含两元素的数组,前者为键名后者为键值。其存储的结构相似Object
,不会出现重复的键名,之中使用的相等断定方法与Set
一致。其实例的字符串标签为[Object Map]
。函数
其与对象主要有两点不一样。一是键名,对象的键名只能是字符串或Symbol
值,而Map
能够是任意类型,它提供了更为完善的值对值的Hash
结构。二是遍历顺序,对象的遍历顺序大体为先数值再字符串后Symbol
值(会在遍历小节中介绍),而Map
是简单的与存储顺序保持一致,这在实际操做中比较有用。优化
let map = new Map([[1, 'one'], [2, 'two']]); console.log(...map); // [1,'one'] [2, 'two'] console.log({}.toString.call(map)); // [Object Map] let o = {}; [...new Map([[o, 1], [o, 2]])]; // [[o, 2]] [...new Map([[{}, 1], [{}, 2]])]; // [[{}, 2], [{}, 2]] [...new Map([[null, 1], [undefined, 2]])]; // [[null, 1], [undefined, 2]] let o = { 0: [1, '1'], 1: [2, '2'], length: 2, [Symbol.iterator]: Array.prototype[Symbol.iterator] }; [...new Map(o)]; // [[1, '1'], [2, '2']]
这里会将Set
与Map
放置在一块儿,在操做方法和遍历方法上进行异同性说明,方便区分和记忆。另外,为了在方法操做上统一Set
与Map
,如Set
小节中说起的,咱们能够认为Set
是键名与键值为同一值的存在(JS自己就是这样作的)。prototype
二者都有的操做方法
判断(has
),传入键名,返回布尔值。
删除(delete
),传入键名,有且成功删除为true
,不然为false
。
清空(clear
),无需传参,没有返回值。
Set
独有的操做方法
新增(add
),传入值,返回实例自己。
没有相应的获取方法,由于获取需传入的值就是应所传出的值。
let set = new Set(); set.add(1).add(NaN); // set.size 为 2。 set.has(NaN); // true set.delete(NaN); // true set.has(NaN); // false set.delete(2); // false set.clear(); console.log(...set); // 它变得一无全部,只剩一具空壳。
Map
独有的操做方法
新增(set
),传入键名和键值,有则更新没则新增,返回实例自己。
获取(get
),传入键名,返回相应值没有则为undefined
。
let o = [1, 2, 3]; let map = new Map(); map.set(o, 1).set(o, 2); // map.size 为 1 map.has(o); // true map.get(o); // 2 map.get([1, 2, 3]); // undefined map.delete(o); // true map.clear(); console.log(...map); // 它再次一无全部,只剩愈发饥渴的兽心。
返回键名的遍历器对象(keys
)。
返回键值的遍历器对象(values
)。
返回键值对的遍历器对象(entries
),键值对为[键名, 键值]
。
遍历每一个成员(forEach
),使用方式与Array
的方法相同。
由于Set
的键名和键值相同,因此通常只使用values
方法获取所有值。而Map
则根据相应需求获取便可。
let set = new Set([1, 2, 3]); let map = new Map([[1, 'one'], [null, NaN]]); [...set.values()]; // [1, 2, 3] [...set.keys()]; // [1, 2, 3] [...set.entries()]; // [[1, 1], [2, 2], [3, 3]] [...map.values()]; // ['one', NaN] [...map.keys()]; // [1, null] [...map.entries()]; // [[1, 'one'], [null, NaN]]
二者的forEach
方法与数组的不一样点在于回调函数的第二个参数,前者为该项的键名后者为该项的序号。
let set = new Set([1, 2, 3]); let map = new Map([[1, 'one'], [null, NaN]]); set.forEach((v, i) => console.log(i)); // 1, 2, 3 map.forEach((v, i) => console.log(i)); // 1, null [1, 2, 3].forEach((v, i) => console.log(i)); // 0, 1, 2
对象属性的遍历顺序
不一样遍历对象的方法面向的数据种类不一样,但总的说其遍历顺序是这样的:先找到其中可转化成数值的属性并按升序遍历,再遍历字符串属性按加入时间的先后,最后遍历Symbol
值按加入时间的先后。Map
的遍历顺序即其被插入时的顺序,嗯,总有些色咪咪的味道。
let obj = { '0': 0, 1: 1, 'b': 2, 'a': 3, [Symbol(2)]: 4, [Symbol(1)]: 5 }; Reflect.ownKeys(obj); // ["0", "1", "b", "a", Symbol(2), Symbol(1)]
弱引用集合WeakSet
和WeakMap
是由Set
和Map
分别衍生出的,其与本体的异同点一致,所以只对WeakMap
进行说明。
在JS的垃圾回收机制中,对象会在没有引用时(可意为使用)被回收。这说明着,若是有个数据集合(数组、对象、Set
或Map
)中包含了某对象(在使用它),那么在此数据集合被回收以前该对象都不能被回收。这很容易致使内存泄漏(专有名词,可简单理解为内存被没用的数据占据)。
弱引用集合的设计目的就是为了解决这个问题。弱引用顾名思义是指虽然某对象被此集合引用了,但该引用不被引擎保护,不被垃圾回收装置考虑在内,该回收时就得乖乖的被回收。那些被惯成畸形的家伙们,要知道,妈妈的怀抱可不是个万全的地方哦,惟有死神的才是。
WeakMap
的行为与Map
除了如下几点不一样外,能够认为是一致的。 WeakMap
的键名只能是对象(不包括null
),否者报错。若是能放入普通类型,那有什么意义呢? WeakMap
的键名是动态不定的,不知道何时会被回收。键名指代的对象被回收后,该项会被自动消除。
由于项数的动态性,因此不能被遍历(没有遍历方法),没有size
属性,没有cealr
方法。
let o = {}; let wm = new WeakMap(); wm.set(1, 1); // 报错,1 不是对象。 wm.set(o, 1); wm.has(o); // true wm.get(o); // 1 wm.delete(o); // true 'size' in wm; // false 'clear' in wm; // false 'values' in wm; // false
弱引用集合的优势在于,咱们能够任意为其注册对象,而不用担忧内存泄漏。典型的应用场景是将DOM
与数据进行绑定。一些要在DOM
中绑定数据的库中,好比d3
,会直接在DOM
对象上设置属性进行保存。但在平常组建单页面程序中的某个阶段,想将DOM
与数据联系在一块儿时,咱们显然会优先选用数据映射的方式。而弱引用集合的出现,更加优化了这种方式。
在下面的示例中,每次点击请求数据后都会生成帮了相应数据项的li
标签,并将该标签与相应的数据进行绑定。在这一系列轮回存储绑定中,由于WeakMap
的弱引用特性,咱们不须要关心已经被删除的DOM
元素。每次只需进行相同的操做,方便安心,省时省力。
<button onclick="requestData()">Request Data</button> <ul id="container"></ul> <script> "use strict"; let wmap = new WeakMap(); let container = document.querySelector('#container'); function requestData() { container.innerHTML = ''; [0, 0, 0].map(() => Math.random()).forEach(d => { let li = document.createElement('li'); li.innerHTML = ` <button onclick="showMes(this)">Show Message</button> <button onclick="deleteItem(this)">Delete Item</button> `; wmap.set(li, d); container.appendChild(li); }); } function showMes(that) { let li = that.parentNode; li.innerHTML = wmap.get(li); } function deleteItem(that) { let li = that.parentNode; container.removeChild(li); } </script>
ES6精华:Symbol
ES6精华:解构赋值
ES6精华:函数扩展
ES6精华:Proxy & Reflect
Iterator:访问数据集合的统一接口