在以前,JS的数据结构只有 对象(Object) 以及 数组(Array),而后来,JS迎来了两种新的数据结构,这两个分别是 Set 和 Map 数据结构。

Set 数据结构

使用技巧

定义:类似于数组,但成员都是唯一的,没有重复的值。使用如下:

  1. const S = new Set();
  2. [2,3,2,5,6,5,8,3].forEach(x => S.add(x));
  3. for(let i of s){
  4. console.log(i) //2 3 5 6 8
  5. }

最后输出的结果为 2 3 5 6 8。

或者,set 可以接受一个数组,如下:

  1. const S = new Set([1, 2, 4, 5, 4, 7, 7])
  2. [...S] // [1, 2, 4, 5, 7]

还可以这样使用:

  1. //去除数组的重复成员
  2. [...new Set(array)]

字符串同样适用:

  1. [...new Set('ababc')].join('') //"abc"

还有就是,如果想把 Set 转为 Array,可以使用 Array 的 from 方法,如下实现:

  1. const items = new Set([1,2,3,4,5,6,7]);
  2. const array = Array.from(items);
  3. //or
  4. function dedupe(array) {
  5. return Array.from(new Set(array));
  6. }
  7. dedupe([1,1,2,3])

注意

虽然在以前的知识里 NaN 不等于 NaN ,但是在 Set 里,认为 NaN 是同一样东西,如下例子:

  1. let set = new Set()
  2. let a = NaN;
  3. let b = NaN;
  4. set.add(a)
  5. set.add(b)
  6. set //Set(1) {NaN}

最终输出的结果只有一个 NaN ,表明在Set内部,NaN是相等的!

与之相反的是,两个对象总是不相等的,如下:

  1. let set = new Set();
  2. set.add({});
  3. set.size //1
  4. set.add({})
  5. set.size //2

向set数据结构连续添加两个都为空的对象,结果是不相等的,被视为两个不相等的值。

实例方法

Set结构的实例有以下属性:

  • Set.proptotype.constructor
  • Set.proptotype.size 返回Set实例的成员总数

操作方法:

  • Set.proptotype.add(value) :添加某个值,返回Set结构本身。
  • Set.proptotype.delete(value):删除一个属性,并返回一个布尔值,表示是否删除成功。
  • Set.proptotype.has(value):返回一个布尔值,表示该值是否为set的成员。
  • Set.proptotype.clear() :清除所有成员,没有返回值。

使用如下:

  1. const S = new Set();
  2. S.add(1).add(2).add(3).add(3)
  3. //注意 数字3 被加入两次了
  4. //输出结果为 {1, 2, 3}
  5. S.size //3
  6. S.has(1) //true
  7. S.has(2) //ture
  8. S.has(3) //ture
  9. S.has(4) //false
  10. S.delete(2) //true
  11. S.has(2) //false

遍历操作:

  • Set.proptotype.keys() :返回 键名 的遍历器
  • Set.proptotype.values() :返回 键值 的遍历器
  • Set.proptotype.entries() :返回 键值对 的遍历器
  • Set.proptotype.forEach() :使用回调函数遍历每个成员

特别注意,Set的遍历顺序就是插入顺序!例如有一个回调函数列表,调用时就能保证按照添加顺序调用。

同时有一点需要指出,Set是没有键名,只有键值,或者说键名和键值是同一个值,所以keys方法和values方法的行为完全一致 !

  1. let set = new Set(['red', 'green', 'blue'])
  2. for(let item of set.keys()){
  3. console.log(item)
  4. }
  5. //red
  6. //green
  7. //blue
  8. //与上一个写法等同
  9. for(let item of set.values()){
  10. console.log(item)
  11. }
  12. //red
  13. //green
  14. //blue
  15. for(let item of set.entries()){
  16. console.log(item)
  17. }
  18. //["red", "red]
  19. //["green", "green]
  20. //["blue", "blue"]

因为Set没有键名,或者说键名就是键值,所以entries返回的键值与键名是相同的。

forEach()方法在Set与Array没什么不同,同样不会有返回值,对每个成员执行某种操作。

WeakSet

与Set类似,同样内部成员不可重复,但是,有以下区别:

  • WeakSet的成员只能是对象,而不是其他类型的值。
  • WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用。

这里插入垃圾回收的机制:
垃圾回收是根据对象的“可达性”来决定是否释放该对象的内存,如果对象还能被访问得到,垃圾回收机制就不会释放该对象内存,反之亦然!但有时候,如果忘记取消引用,导致内存无法释放,进而导致内存泄漏。

所以,WeakSet适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在WeakSet里面的引用就会自动消失!

也因为这样,WeakSet的内部成员数量不可确定,运行前与运行后的数量随时会变,所以ES6规定WeakSet不可被遍历。

语法

  1. const WS = new WeakSet()

只能接受对象:

  1. const a = [[1,2],[3,4]]
  2. const WS = new WeakSet(a)
  3. //结果为:{[1, 2], [3, 4]}

看似上面的数组不是对象,实际上a的成员会自动的变成 WeakSet的成员,而不是a本身会成为WeakSet的成员。如下例子:

  1. const a = [1,2]
  2. const ws = new WeakSet(a)

实例方法

WeakSet有以下三个方法:

  • WeakSet.prototype.add(value):向WeakSet 实例添加一个新成员。
  • WeakSet.prototype.delete(value):清除WeakSet实例的指定成员。
  • WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在WeakSet实例之中。 ```javascript const WS = new WeakSet(); const obj = {}; const foo = {};

WS.add(window); WS.add(obj);

WS.has(window) //true WS.has(foo) //false

WS.delete(window) WS.has(window) //false

  1. <a name="TQnNQ"></a>
  2. ## Map
  3. 定义: 类似于对象,也就是键值对的集合,但是键名的范围不仅仅是字符串,也就是Object提供的“字符串 - 值”,而是Map提供的“值 - 值”对应,是一种更完善的 Hash 结构实现。如下:
  4. ```javascript
  5. const M = new Map()
  6. const O = {P: 'Hello World'}
  7. M.set(O, 'content')
  8. M.get(O) //"content"
  9. M.has(O) //true
  10. M.delete(O) //true
  11. M.has(O) //false

上面的O作为字符串 content 的键值,并用get 方法读取O的键值。

还可以把数组作为键值对,如下用法:

  1. const map = new Map([
  2. ['name', '张三'],
  3. ['title', 'Author']
  4. ])
  5. map.size //2
  6. map.has('name') //true
  7. map.get('name') //"张三"

注意如果对同一个键赋值多次,那么后一次会覆盖前一次。如下:

  1. const map = new Map()
  2. map
  3. .set(1, 'aaa')
  4. .set(1, 'bbb')
  5. .set(2, 'ccc')
  6. // Map(2) {1 => "bbb", 2 => "ccc"}

如果读取一个未知的键,就会返回 undefined

  1. new Map().get('不知道的键') //underfined

更要注意的是,只有对同一个对像的引用,Map结构才视为同一个键:

  1. const map = new Map()
  2. map.set(['a'], 555);
  3. map.get(['a']) //undefined

看似set和get针对同一个键,但这两个是不同的数组,内存地址不一样,所以不会被视为同一个键!如下才会被视为同一个键:

  1. const map = new Map()
  2. const k1 = ['a'];
  3. const k2 = ['a'];
  4. map.set(k1, 111).set(k2, 222)
  5. map.get(k1) //111
  6. map.get(k2) //222

也就是说,只要两个键的名字严格相等,那么这两个键就是同一个键!同理 , NaN 在Map这,是相等的!如下:

  1. let map = new Map()
  2. map.set(-0, 123)
  3. map.get(+0)
  4. map.set(true, 1)
  5. map.set('true', 2)
  6. map.get(true) //1
  7. map.set(undefined, 3)
  8. map.set(null, 4)
  9. map.get(undefined) //3
  10. map.set(NaN, 123)
  11. map.set(NaN, 456)
  12. map.get(NaN) //456

实例的属性

如下:

  • size:返回Map结构的成员总数。
  • Map.prptotype.set(key, value) :设置键名 key 对应的键值为 value ,然后返回整个Map结构。
  • Map.prptotype.set(key) :读取 key 对应的键值,如果找不到,就返回 undefined
  • Map.prptotype.has(key)has 方法返回一个布尔值,表示是否在当前的Map对象中。
  • Map.prptotype.delete(key) :删除某个键,成功返回 true ,反之为 false
  • Map.prptotype.clear() :清空所有成员,没有返回值。

遍历方法:

  • Map.prototype.keys() :返回键名的遍历器。
  • Map.prototype.values() :返回键值的遍历器。
  • Map.prototype.entries() :返回所有成员的遍历器。
  • Map.prototype.forEach() :遍历Map的所有成员。

除了Map有键值对,Set没有外,方法其实是没差多少的!

互换数据结构

  1. Map转数组

直接使用扩展字符串即可:

  1. const myMap = new Map()
  2. myMap.set(true, 7).set({foo: 3}, ['abc'])
  3. [...myMap] // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
  1. 数组转Map

将数组传给Map便可:

  1. new Map([
  2. [ true, 7 ],
  3. [ { foo: 3 }, [ 'abc' ] ]
  4. ])
  5. // Map {
  6. // true => 7,
  7. // Object {foo: 3} => ['abc']
  8. // }
  1. Map转对象

如下:

  1. function strMapToObj(strMap){
  2. let obj = Object.create(null);
  3. for(let [k, v] of strMap){
  4. obj[k] = v;
  5. }
  6. return obj;
  7. }
  8. const myMap = new Map()
  9. .set('yes', true)
  10. .set('no', false)
  11. strMapToObj(myMap) //{yes: true, no: false}

如果键名为字符串,就会无损的转为对象的字符串,如果不是,就会先转成字符串,再变成对象!

  1. 对象转Map

方法如下:

  1. let obj = {"a": 1, "b", 2};
  2. let map = new Map(Objetc.entries(obj));

WeakMap

定义:与Map类似,但不同在于,键名只接受对象类型(null除外),其他类型不接受。

  1. const WM = new WeakMap();
  2. const key = {foo: 1}
  3. WM.set(key, 2)
  4. WM.get(key) //2
  5. const k1 = [1,2,3]
  6. const k2 = [4,5,6]
  7. const WM = new WeakMap([[k1, 'foo'], [k2, 'bar']])
  8. WM.get(k2) //"bar"

作用与WeakSet差不多,都是为了降低内存泄漏风险而设计出来的目的,典型的场景就是在网页的DOM元素上添加数据,就可以使用 WeakMap 结构,当该DOM元素被清除,对应的 WeakMap 就会被清除。

  1. const WM = new WeakMap();
  2. const element = document.getElementById('example');
  3. WM.set(element, 'some information');
  4. WM.get(element) //"som information"

注意,只有键名是弱引用的,键值可以正常引用!因为键名是弱引用,该引用一旦消失,那么键值对也会跟着消失!

语法

当然,没有遍历操作,只有四个方法可用:

  • get()
  • set()
  • has()
  • delete()