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]