在以前,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);
//or
function 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 //1
set.add({})
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()
:清除所有成员,没有返回值。
使用如下:
const S = new Set();
S.add(1).add(2).add(3).add(3)
//注意 数字3 被加入两次了
//输出结果为 {1, 2, 3}
S.size //3
S.has(1) //true
S.has(2) //ture
S.has(3) //ture
S.has(4) //false
S.delete(2) //true
S.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
//blue
for(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 结构实现。如下:
```javascript
const M = new Map()
const O = {P: 'Hello World'}
M.set(O, 'content')
M.get(O) //"content"
M.has(O) //true
M.delete(O) //true
M.has(O) //false
上面的O作为字符串 content 的键值,并用get
方法读取O的键值。
还可以把数组作为键值对,如下用法:
const map = new Map([
['name', '张三'],
['title', 'Author']
])
map.size //2
map.has('name') //true
map.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) //111
map.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) //1
map.set(undefined, 3)
map.set(null, 4)
map.get(undefined) //3
map.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
,反之为false
Map.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) //2
const 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()