JavaScript------Map,Set详解
JavaScript------Map,Set详解
Map
ES6之前,在JavaScript中实现
键值对存储可以使用Object完成,使用对象属性作为键,使用属性作为引用值ES6中新增Map,一种新的集合类型,为JavaScript这门语言带来真正的
键值对存储机制
创建映射
- 创建空映射
  const m=new Map()
- 使用嵌套数组初始化映射
  const m1=new Map([['key1','val1'],['key2','val2'],['key3','val3']])
  console.log(m1.size)//3
- 映射期待的键值对,无论是否提供
  const m2=new Map([[]])
  console.log(m2.has(undefined))//true
  console.log(m2.get(undefined))//undefined
初始化映射之后
- get()方法用来获取一个 Map 对象中指定的元素。
- set()方法为Map对象添加一个指定键(key)和值(value)的新元素。
- has()返回一个- bool值,用来表明map 中是否存在指定元素。
- delete(key)只删除一个键值对
- clear()删除所有键值对
  const m3 = new Map([['aaa', '111'], ['bbb', '222'], ['ccc', '333']])
  console.log(m3.size)//3
  m3.set("ddd", 444)
  console.log(m3.keys()) // {'aaa', 'bbb', 'ccc', 'ddd'}
  console.log(m3.values()) //{'111', '222', '333', 444}
  console.log(m3.size) //4
  console.log(m3.has("ddd")) //true
  //传入键名
  console.log(m3.delete('ddd'))//true
  console.log(m3.size)//3
  //清空所有键值对
  m3.clear()
  console.log(m3.size)//0
set()方法返回映射实例,可以把多个操作连接起来,包括初始化声明const m4 = new Map().set('111', 'aaa') m4.set('222', 'bbb') .set('333', 'ccc') console.log(m4.size) //3
注意: 与Object只能使用数值,字符串或者符号作为键不同,Map可以使用任何JavaScript数据类型作为键
  const m = new Map()
  const functionKey = function () { }
  const symbolKey = Symbol()
  const objectKey = new Object()
  m.set(functionKey, "functionValue")
  m.set(symbolKey, "symbolValue")
  m.set(objectKey, "objectValue")
  console.log(m.get(functionKey))//functionValue
  console.log(m.get(symbolKey))//symbolValue
  console.log(m.get(objectKey))//objectValue
在映射中用作键和值的的对象及其他’集合’类型,在自己的内容或属性被修改时仍然保持不变
 const m = new Map()
  const objKey = {}, objVal = {}, arrKey = [], arrVal = [];
  m.set(objKey, objVal)
  m.set(arrKey, arrVal)
  objKey.foo = "foo"
  objVal.bar = "bar"
  arrKey.push("foo")
  arrVal.push('val')
  console.log(m.get(objKey))//{bar: 'bar'}
  console.log(m.get(arrKey))//['val']
顺序与迭代
- 与Object类型的一个主要差异,Map实例会维护键值对的插入顺序,映射实例可以提供一个迭代器(Iterator),能以插入顺序生成[key,value]形式的数组,通过entries()方法(或者Symbol.iterator属性) 取得这个迭代器
 const m = new Map([['key1', 'val1'], ['key2', 'val2'], ['key3', 'val3']])
  console.log(m.entries === m[Symbol.iterator])//true
  for (let item of m.entries()) {
    console.log(item)
  }//['key1','val1'],['key2','val2'],['key3','val3']
  for (let item of m[Symbol.iterator]()) {
    console.log(item)
  }//['key1','val1'],['key2','val2'],['key3','val3']
- entries()是默认迭代器,可以直接对映射实例使用扩展运算,把映射转换为数组
 console.log([...m]) //[ ['key1', 'val1'], ['key2', 'val2'],['key3', 'val3']]
- 使用回调方式,调用映射的forEach()方法并传入回调
 m.forEach((val, key) => console.log(`${key}->${val}`))
  // key1->val1
  // key2->val2
  // key3->val3
  for(let e of m.keys()){
    console.log(e)
  } //key1 key2 key3
  for(let e of m.values()){
    console.log(e)
  } //val1 val2 val3
- 键和值在迭代器遍历时是可以修改的,但映射内部的引用无法修改(可以修改作为键或值的对象内部的属性),键的字符串的原始值是不能修改的 - 修改了作为键的对象的属性,但对象在映射内部仍然引用相同的值 
  const keyObj={id:1}
  const m=new Map([[keyObj,'val1']])
  for(let key of m.keys())
  {
    key.id="newKey"
    console.log(key)//{id:"newKey"}
    console.log(m.get(keyObj));//val1
  }
  console.log(keyObj) //{id:"newKey"}
Map和Object
选择Map还是Object
对于多数 Web 开发任务来说,选择 Object 还是 Map 只是个人偏好问题,影响不大。不过,对于在乎内存和性能的开发者来说,对象和映射之间确实存在显著的差别。------《JavaScript高级程序设计》
- 内存占用 - Object和- Map的工程级实现在不同浏览器间存在明显差异,但存储单个键/值对所占用的内存数量.都会随键的数量线性增加。批量添加或删除键/值对则取决于各浏览器对该类型内存分配的工程实现。不同浏览器的情况不同,但给定固定大小的内存,- Map大约可以比- Object多存储 50%的键/值对。
- 插入性能 向 - Object和- Map中插入新键/值对的消耗大致相当,不过插入- Map在所有浏览器中一般会稍微快一点儿。对这两个类型来说,插入速度并不会随着键/值对数量而线性增加。如果代码涉及大量插入操作,那么显然- Map的性能更佳。
- 查找速度与插入不同,从大型 - Object和- Map中查找键/值对的性能差异极小,但如果只包含少量键/值对,则- Object有时候速度更快。在把- Object当成数组使用的情况下(比如使用连续整数作为属性),浏览器引擎可以进行优化,在内存中使用更高效的布局。这对- Map来说是不可能的。对这两个类型而言,查找速度不会随着键/值对数量增加而线性增加。如果代码涉及大量查找操作,那么某些情况下可能选择- Object更好一些。
- 删除性能使用 - delete删除- Object属性的性能一直以来饱受诟病,目前在很多浏览器中仍然如此。为此,出现了一些伪删除对象属性的操作,包括把属性值设置为- undefined或- null。但很多时候,这都是一种讨厌的或不适宜的折中。而对大多数浏览器引擎来说,- Map的- delete()操作都比插入和查找更快。如果代码涉及大量删除操作,那么毫无疑问应该选择- Map。
应用场景
- Map - 会频繁的更新和删除键值对时
- 存储大量数据时,尤其是key类型未知的情况下
- 需要频繁进行迭代处理
 
- Object - 仅做数据存储,并且属性仅为字符串或者Symbol
- 需要进行序列化转换为json传输时
- 当做一个对象的实例,需要保留自身的属性和方法时
 
Set
ES6新增Set是一种新集合类型,为这门语言带来了集合数据类型,在很多方面,Set像加强的Map
Set创建
- 通过Set的构造函数创建Set(没有字面量创建的方式),可以先创建一个空的Set对象,随后使用add方法添加元素; - const obj = { name: "aaa" } const set1 = new Set() set1.add(10) set1.add(10) set1.add(12) set1.add(14) set1.add(obj) console.log(set1)//Set(4) {10, 12, 14, {…}} 对象也是可以做元素的
- 直接添加元素 - const set2 = new Set([10, 10, 12, 14, {name: "zzz"}]) console.log(set2) console.log(set2)//Set(4) {10, 12, 14, {…}}- 两种方法都创建了4个元素的Set 对象也是可以做元素的 
Set常见属性,方法
- Set常见的属性:size:返回Set中元素的个数 
- Set常用的方法: - add(value):添加某个元素,返回Set对象本身;
 - const s = new Set(); s.add(1).add(2).add(3); Array.from(s); // [1, 2, 3]- delete(value):从set中删除和这个值相等的元素,返回boolean类型;
 - const s = new Set(); s.add(1).add(2); s.delete(1); Array.from(s); // [2]- has(value):判断set中是否存在某个元素,返回boolean类型;
 - const s = new Set(); s.add(1).add(2).add(3); s.has(1); // true- clear():清空set中所有的元素,没有返回值;
 - const s = new Set(); s.add(1).add(2).add(3); Array.from(s); // [1, 2, 3] s.clear(); Array.from(s); // []
迭代
- forEach:通过forEach遍历set
const s = new Set();
s.add(1).add(2).add(3);
s.forEach((value, key) => console.log(key + ' : ' + value));
// 1 : 1
// 2 : 2
// 3 : 3
- for of
const s = new Set();
s.add(1).add(2).add(3);
for (const i of s) {
	console.log(i);
}
// 1
// 2
// 3
- 键名=键值
const s = new Set();
s.add(1).add(2).add(3);
Array.from(s.keys()); // [1, 2, 3]
Array.from(s.values()); // [1, 2, 3]
Array.from(s.entries()); // [[1, 1], [2, 2], [3, 3]]
Set去重
const arr = [1, 2, 3, 3, 4, 5, 4, 4, 2, 1, 3];
Array.from(new Set(arr)); // [1, 2, 3, 4, 5]