在以前,JS的数据结构只有 对象(Object) 以及 数组(Array),而后来,JS迎来了两种新的数据结构,这两个分别是 Set 和 Map 数据结构。
Set 数据结构
使用技巧
定义:类似于数组,但成员都是唯一的,没有重复的值。使用如下:
const S = new Set();[2,3,2,5,6,5,8,3].forEach(x => S.add(x));for(let i of s){console.log(i) //2 3 5 6 8}
最后输出的结果为 2 3 5 6 8。
或者,set 可以接受一个数组,如下:
const S = new Set([1, 2, 4, 5, 4, 7, 7])[...S] // [1, 2, 4, 5, 7]
还可以这样使用:
//去除数组的重复成员[...new Set(array)]
字符串同样适用:
[...new Set('ababc')].join('') //"abc"
还有就是,如果想把 Set 转为 Array,可以使用 Array 的 from 方法,如下实现:
const items = new Set([1,2,3,4,5,6,7]);const array = Array.from(items);//orfunction dedupe(array) {return Array.from(new Set(array));}dedupe([1,1,2,3])
注意
虽然在以前的知识里 NaN 不等于 NaN ,但是在 Set 里,认为 NaN 是同一样东西,如下例子:
let set = new Set()let a = NaN;let b = NaN;set.add(a)set.add(b)set //Set(1) {NaN}
最终输出的结果只有一个 NaN ,表明在Set内部,NaN是相等的!
与之相反的是,两个对象总是不相等的,如下:
let set = new Set();set.add({});set.size //1set.add({})set.size //2
向set数据结构连续添加两个都为空的对象,结果是不相等的,被视为两个不相等的值。
实例方法
Set结构的实例有以下属性:
Set.proptotype.constructorSet.proptotype.size返回Set实例的成员总数
操作方法:
Set.proptotype.add(value):添加某个值,返回Set结构本身。Set.proptotype.delete(value):删除一个属性,并返回一个布尔值,表示是否删除成功。Set.proptotype.has(value):返回一个布尔值,表示该值是否为set的成员。Set.proptotype.clear():清除所有成员,没有返回值。
使用如下:
const S = new Set();S.add(1).add(2).add(3).add(3)//注意 数字3 被加入两次了//输出结果为 {1, 2, 3}S.size //3S.has(1) //trueS.has(2) //tureS.has(3) //tureS.has(4) //falseS.delete(2) //trueS.has(2) //false
遍历操作:
Set.proptotype.keys():返回 键名 的遍历器Set.proptotype.values():返回 键值 的遍历器Set.proptotype.entries():返回 键值对 的遍历器Set.proptotype.forEach():使用回调函数遍历每个成员
特别注意,Set的遍历顺序就是插入顺序!例如有一个回调函数列表,调用时就能保证按照添加顺序调用。
同时有一点需要指出,Set是没有键名,只有键值,或者说键名和键值是同一个值,所以keys方法和values方法的行为完全一致 !
let set = new Set(['red', 'green', 'blue'])for(let item of set.keys()){console.log(item)}//red//green//blue//与上一个写法等同for(let item of set.values()){console.log(item)}//red//green//bluefor(let item of set.entries()){console.log(item)}//["red", "red]//["green", "green]//["blue", "blue"]
因为Set没有键名,或者说键名就是键值,所以entries返回的键值与键名是相同的。
forEach()方法在Set与Array没什么不同,同样不会有返回值,对每个成员执行某种操作。
WeakSet
与Set类似,同样内部成员不可重复,但是,有以下区别:
- WeakSet的成员只能是对象,而不是其他类型的值。
- WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用。
这里插入垃圾回收的机制:
垃圾回收是根据对象的“可达性”来决定是否释放该对象的内存,如果对象还能被访问得到,垃圾回收机制就不会释放该对象内存,反之亦然!但有时候,如果忘记取消引用,导致内存无法释放,进而导致内存泄漏。
所以,WeakSet适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在WeakSet里面的引用就会自动消失!
也因为这样,WeakSet的内部成员数量不可确定,运行前与运行后的数量随时会变,所以ES6规定WeakSet不可被遍历。
语法
const WS = new WeakSet()
只能接受对象:
const a = [[1,2],[3,4]]const WS = new WeakSet(a)//结果为:{[1, 2], [3, 4]}
看似上面的数组不是对象,实际上a的成员会自动的变成 WeakSet的成员,而不是a本身会成为WeakSet的成员。如下例子:
const a = [1,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
<a name="TQnNQ"></a>## Map定义: 类似于对象,也就是键值对的集合,但是键名的范围不仅仅是字符串,也就是Object提供的“字符串 - 值”,而是Map提供的“值 - 值”对应,是一种更完善的 Hash 结构实现。如下:```javascriptconst M = new Map()const O = {P: 'Hello World'}M.set(O, 'content')M.get(O) //"content"M.has(O) //trueM.delete(O) //trueM.has(O) //false
上面的O作为字符串 content 的键值,并用get 方法读取O的键值。
还可以把数组作为键值对,如下用法:
const map = new Map([['name', '张三'],['title', 'Author']])map.size //2map.has('name') //truemap.get('name') //"张三"
注意如果对同一个键赋值多次,那么后一次会覆盖前一次。如下:
const map = new Map()map.set(1, 'aaa').set(1, 'bbb').set(2, 'ccc')// Map(2) {1 => "bbb", 2 => "ccc"}
如果读取一个未知的键,就会返回 undefined :
new Map().get('不知道的键') //underfined
更要注意的是,只有对同一个对像的引用,Map结构才视为同一个键:
const map = new Map()map.set(['a'], 555);map.get(['a']) //undefined
看似set和get针对同一个键,但这两个是不同的数组,内存地址不一样,所以不会被视为同一个键!如下才会被视为同一个键:
const map = new Map()const k1 = ['a'];const k2 = ['a'];map.set(k1, 111).set(k2, 222)map.get(k1) //111map.get(k2) //222
也就是说,只要两个键的名字严格相等,那么这两个键就是同一个键!同理 , NaN 在Map这,是相等的!如下:
let map = new Map()map.set(-0, 123)map.get(+0)map.set(true, 1)map.set('true', 2)map.get(true) //1map.set(undefined, 3)map.set(null, 4)map.get(undefined) //3map.set(NaN, 123)map.set(NaN, 456)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,反之为falseMap.prptotype.clear():清空所有成员,没有返回值。
遍历方法:
Map.prototype.keys():返回键名的遍历器。Map.prototype.values():返回键值的遍历器。Map.prototype.entries():返回所有成员的遍历器。Map.prototype.forEach():遍历Map的所有成员。
除了Map有键值对,Set没有外,方法其实是没差多少的!
互换数据结构
- Map转数组
直接使用扩展字符串即可:
const myMap = new Map()myMap.set(true, 7).set({foo: 3}, ['abc'])[...myMap] // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
- 数组转Map
将数组传给Map便可:
new Map([[ true, 7 ],[ { foo: 3 }, [ 'abc' ] ]])// Map {// true => 7,// Object {foo: 3} => ['abc']// }
- Map转对象
如下:
function strMapToObj(strMap){let obj = Object.create(null);for(let [k, v] of strMap){obj[k] = v;}return obj;}const myMap = new Map().set('yes', true).set('no', false)strMapToObj(myMap) //{yes: true, no: false}
如果键名为字符串,就会无损的转为对象的字符串,如果不是,就会先转成字符串,再变成对象!
- 对象转Map
方法如下:
let obj = {"a": 1, "b", 2};let map = new Map(Objetc.entries(obj));
WeakMap
定义:与Map类似,但不同在于,键名只接受对象类型(null除外),其他类型不接受。
const WM = new WeakMap();const key = {foo: 1}WM.set(key, 2)WM.get(key) //2const k1 = [1,2,3]const k2 = [4,5,6]const WM = new WeakMap([[k1, 'foo'], [k2, 'bar']])WM.get(k2) //"bar"
作用与WeakSet差不多,都是为了降低内存泄漏风险而设计出来的目的,典型的场景就是在网页的DOM元素上添加数据,就可以使用 WeakMap 结构,当该DOM元素被清除,对应的 WeakMap 就会被清除。
const WM = new WeakMap();const element = document.getElementById('example');WM.set(element, 'some information');WM.get(element) //"som information"
注意,只有键名是弱引用的,键值可以正常引用!因为键名是弱引用,该引用一旦消失,那么键值对也会跟着消失!
语法
当然,没有遍历操作,只有四个方法可用:
get()set()has()delete()
