目录
Map
映射(Map)是 ECMAScript 6 规范中引入的一种数据结构。这是一种存储键值对列表很方便的方法,类似于其他编程语言中的词典或者哈希表。常用的 Map
方法有:
- 赋值
set(key, value)
、 - 获取
get(key)
、 - 移除指定键名及其对应的值
delete(key)
、 - 判断是否存在
has(key)
、 - 获取所有值
values()
、 key/value
迭代器entries()
、- 清空所有键/值对
clear()
等。
JavaScript 的对象
Object
,本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键,这给使用带来了很大的限制。为了解决这个问题,ECMAScript 6 引入了Map
数据结构。它类似于对象,也是键值对的集合,但是"键
"的范围不仅仅局限于字符串,而是各种类型的值(包括对象)都可以当作键。也就是说,Object
结构(对象结构)提供了"字符串—值"的对应,而Map
结构提供了"值—值"的对应,是一种更完善的Hash
结构的实现。
输出结果为:
Map {
'seo' => {
keywords: 'infoq、Map',
description: 'Map对象是一种简单的键/值映射,其中的键和值可以是任意值(原始值或对象的值)'
},
'title' => 'javascript es6的map映射'
}
object
Map.set()
为数据类型 Map 赋值的方法 map.set(key,value)
,可以用于增加新的键/值对或者修改键/值对,返回整个 Map
对象。
myMap.set(key, value);
key
:要添加至相应 Map 对象的元素的键。value
:要添加至相应 Map 对象的元素的值。
返回值 返回 Map 对象。
const page_info = new Map()
// 设置值
page_info.set("seo", {
"keywords": "infoq、Map",
"description": "Map对象是一种简单的键/值映射,其中的键和值可以是任意值(原始值或对象的值)"
});
console.log(page_info);
page_info.set("seo", "seo信息");
console.log(page_info);
Map.get()
使用 get(key)
获取键值,如果获取的 key->value
不存则返回 undefined
。
myMap.get(key);
key
:必须参数,也是它唯一的参数,要从目标 Map 对象中获取的元素的键。
返回值 返回一个 Map 对象中与指定键相关联的值,如果找不到这个键则返回 undefined
。
const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
const title = page_info.get("title");
const seo_info = page_info.get("seo");
console.log(title); // javascript es6的map映射
console.log(seo_info); // undefinedC
Map.delete()
map.delete(key)
删除指定 key
的键值对,返回成功或失败结果,删除成功返回 true
,删除失败返回 false
。
myMap.delete(key);
key
:必须,从 Map 对象中移除的元素的键。
返回值 返回值为一个 Boolean
值,如果 Map 对象中存在该元素,则移除它并返回 true
;否则如果该元素不存在则返回 false
。
const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
page_info.set("author", "infoq");
console.log(page_info); // Map { 'title' => 'javascript es6的map映射', 'author' => 'infoq' }
const deleted_author = page_info.delete("author");
const deleted_seo = page_info.delete("seo");
console.log(deleted_author); // true
console.log(deleted_seo); // false
console.log(page_info);
Map.has()
判断指定key是否存在。
语法
myMap.has(key);
key
:必填. 用来检测是否存在指定元素的键值。
返回值 返回值为一个 Boolean
值,如果指定元素存在于 Map 中,则返回 true
,其他情况返回 false
。
const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
console.log(page_info); // Map { 'title' => 'javascript es6的map映射' }
console.log(page_info.has("title")); // true
console.log(page_info.has("seo")); // false
Map.values()
获取所有键的值。
语法
myMap.values()
返回值 一个新的 Map 可迭代对象。
const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
page_info.set("author", "infoq");
console.log(page_info.values()); // [Map Iterator] { 'javascript es6的map映射', 'infoq' }
Map.entries()
返回一个新的包含 [key, value]
对的 Iterator
对象,返回的迭代器的迭代顺序与 Map
对象的插入顺序相同。
语法
myMap.entries()
返回值一个新的 Map 迭代器对象。
const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
page_info.set("author", "infoq");
console.log(page_info.entries());
Map.clear()
移除Map对象中的所有元素。
语法
myMap.clear();
返回值 清除所有元素,返回 undefined
。
const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
page_info.set("author", "infoq");
page_info.clear();
console.log(page_info); // Map {}
选择Object 还是Map
map是键值的映射,对象是作为属性进行保存。
- 内存占用: 给定固定大小的内存,Map 大约可以比Object 多存储50%的键/值对。
- 插入性能:向Object 和Map 中插入新键/值对的消耗大致相当,Map稍快一点。
- 查找速度:如果只包含少量键/值对,则Object 有时候速度更快。在把Object 当成数组使用的情况下(比如使用连续整数作为属性),浏览器引擎可以进行优化,在内存中使用更高效的布局。这对Map 来说是不可能的。对这两个类型而言,查找速度不会随着键/值对数量增加而线性增加。如果代码涉及大量查找操作,那么某些情况下可能选择Object 更好一些。
- 删除性能:使用delete 删除Object 属性的性能一直以来饱受诟病,目前在很多浏览器中仍然如此。为此,出现了一些伪删除对象属性的操作,包括把属性值设置为undefined 或null。但很多时候,这都是一种讨厌的或不适宜的折中。而对大多数浏览器引擎来说,Map 的delete()操作都比插入和查找更快。如果代码涉及大量删除操作,那么毫无疑
数据转换
Map 是一个集合,可以与数组、对象进行转换。
转为数组
Map 转为数组最方便方法是使用扩展运算符 ...
,如下:
const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
page_info.set("author", "infoq");
console.log([...page_info]); // [ [ 'title', 'javascript es6的map映射' ], [ 'author'
转为 JSON
Map 转为 JSON ,步骤是先把 Map 转为对象,即前面的 mapToObj
,然后使用 JSON.stringify
方法,如下:
function mapToObj(map) {
const obj = Object.create(null);
map.forEach((v,k)=>{
obj[k] = v;
});
return obj;
}
function mapToJson(map){
return JSON.stringify(mapToObj(map));
}
const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
page_info.set("author", "infoq");
console.log( mapToJson(page_info)); // {"title":"javascript es6的map映射","author":"infoq"}
对象转为 Map
对象转为 Map 映射通过 Object.entries()
。
const page_info = {
title:"javascript es6的map映射",
author:"infoq"
};
console.log(new Map(Object.entries(page_info))); // Map { 'title' => 'javascript es6的map映射', 'author' => 'infoq' }
数组转为 Map
将数组传入 Map 构造函数即可,即 new Map(array)
,如下:
const page_info = [
["title","javascript es6的map映射"],
["author","infoq"]
];
console.log(new Map(page_info)); // Map { 'title' => 'javascript es6的map映射', 'author' => 'infoq' }
转为Object
function mapToObj(map) {
const obj = Object.create(null);
map.forEach((v, k) => {
obj[k] = v;
});
return obj;
}
const page_info = new Map();
page_info.set("title", "javascript es6的map映射");
page_info.set("author", "infoq");
console.log(mapToObj(page_info));
WeakMap
WeakMap 是Map 的“兄弟”类型,其API 也是Map 的子集。WeakMap 中的“weak”(弱),描述的是JavaScript 垃圾回收程序对待“弱映射”中键的方式。
基本API
可以使用new 关键字实例化一个空的WeakMap:
const wm = new WeakMap();
弱映射中的键只能是Object 或者继承自Object 的类型,尝试使用非对象设置键会抛出TypeError。值的类型没有限制。
const key1 = {id: 1},
key2 = {id: 2},
key3 = {id: 3};
// 使用嵌套数组初始化弱映射
const wm1 = new WeakMap([
[key1, "val1"],
[key2, "val2"],
[key3, "val3"]
]);
alert(wm1.get(key1)); // val1
alert(wm1.get(key2)); // val2
alert(wm1.get(key3)); // val3
键不是对象时,报错:
// 初始化是全有或全无的操作
// 只要有一个键无效就会抛出错误,导致整个初始化失败
const wm2 = new WeakMap([
[key1, "val1"],
["BADKEY", "val2"],
[key3, "val3"]
]);
// TypeError: Invalid value used as WeakMap key
typeof wm2;
// ReferenceError: wm2 is not defined
初始化之后可以使用set()
再添加键/值对,可以使用get()
和has()
查询,还可以使用delete()
删除。
弱键
WeakMap 中“weak”表示弱映射的键是“弱弱地拿着”的。意思就是,这些键不属于正式的引用,不会阻止垃圾回收。但要注意的是,弱映射中值的引用可不是“弱弱地拿着”的。只要键存在,键/值对就会存在于映射中,并被当作对值的引用,因此就不会被当作垃圾回收。
const wm = new WeakMap();
wm.set({}, "val");
set()方法初始化了一个新对象并将它用作一个字符串的键。因为没有指向这个对象的其他引用,所以当这行代码执行完成后,这个对象键就会被当作垃圾回收。然后,这个键/值对就从弱映射中消失了,使其成为一个空映射。在这个例子中,因为值也没有被引用,所以这对键/值被破坏以后,值本身也会成为垃圾回收的目标。
即当这个对象键没有引用时,会被回收,导致这个键值对也会被回收
不可迭代
因为WeakMap 中的键/值对任何时候都可能被销毁,所以并没有给WeakMap提供迭代键值对的能力。
所以无法迭代,所以也不可能在不知道对象引用的情况下从弱映射中取得值。即便代码可以访问WeakMap 实例,也没办法看到其中的内容。
WeakMap实例之所以限制只能用对象作为键,是为了保证只有通过键对象的引用才能取得值。如果允许原始值,那就没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了。
Set
ECMAScript 6 新增的 Set 是一种新集合类型,为这门语言带来集合数据结构。Set 在很多方面都像是加强的 Map,这是因为它们的大多数 API 和行为都是共有的。
创建Set实例
使用 new 关键字和 Set 构造函数可以创建一个空集合:
const s = new Set();
如果想在创建的同时初始化实例,则可以给 Set 构造函数传入一个可迭代对象,其中需要包含插入到新集合实例中的元素(Set 可以包含任何 JavaScript 数据类型作为值):
const s = new Set(["val1", 1, true, {}, undefined, function fun() {}]);
注意:Set结构不会添加重复的值
const s = new Set([1, 1, 2, 3, 4, 4, 5, 6, 7, 4, 2, 1]);
Array.from(s); // [1, 2, 3, 4, 5, 6, 7]
经常用Set解决数组去重问题
const arr = [1, 2, 3, 3, 4, 5, 4, 4, 2, 1, 3];
Array.from(new Set(arr)); // [1, 2, 3, 4, 5]
Set实例转数组
const s = new Set([1, 2, 3]);
Array.from(s); // [1, 2, 3]
size属性
size: 获取Set实例的元素个数:
const s = new Set([1, 2, 3]);
s.size; // 3
add()
add(): 添加元素:
const s = new Set();
s.add(1).add(2).add(3);
Array.from(s); // [1, 2, 3]
has()
has(): 查询Set实例是否存在某元素(返回布尔值):
const s = new Set();
s.add(1).add(2).add(3);
s.has(1); // true
. delete()
delete(): 删除Set实例中某个元素(返回布尔值):
const s = new Set();
s.add(1).add(2);
s.delete(1);
Array.from(s); // [2]
clear()
clear(): 清空Set实例:
const s = new Set();
s.add(1).add(2).add(3);
Array.from(s); // [1, 2, 3]
s.clear();
Array.from(s); // []
迭代
- keys():返回键名;
- values(): 返回键值;
- entries(): 返回键值对;
- 键名=键值
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]]
for-of:
const s = new Set();
s.add(1).add(2).add(3);
for (const i of s) {
console.log(i);
}
// 1
// 2
// 3
forEach
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
WeakSet
WeakSet 是Set 的“兄弟”类型,其API 也是Set 的子集。WeakSet 中的“weak”(弱),描述的是JavaScript 垃圾回收程序对待“弱集合”中值的方式。
可以使用new 关键字实例化一个空的WeakSet
const ws = new WeakSet();
弱集合中的值只能是Object 或者继承自Object 的类型,尝试使用非对象设置值会抛出TypeError。
如果想在初始化时填充弱集合,则构造函数可以接收一个可迭代对象,其中需要包含有效的值。可迭代对象中的每个值都会按照迭代顺序插入到新实例中:
const val1 = {id: 1},
val2 = {id: 2},
val3 = {id: 3};
// 使用数组初始化弱集合
const ws1 = new WeakSet([val1, val2, val3]);
alert(ws1.has(val1)); // true
alert(ws1.has(val2)); // true
alert(ws1.has(val3)); // true
初始化之后可以使用add()
再添加新值,可以使用has()
查询,还可以使用delete()
删除。弱值性质与不可迭代性质与WeakMap的原理相同。
迭代与扩展操作
有4 种原生集合类型定义了默认迭代器:
- Array
- 所有定型数组
- Map
- Set
意味着上述所有类型都支持顺序迭代,都可以传入for-of 循环:
for (const iterableThing of iterableThings) {
for (const x of iterableThing) {
console.log(x);
}
}
意味着所有这些类型都兼容扩展操作符。扩展操作符在对可迭代对象执行浅复制时特别有用,只需简单的语法就可以复制整个对象:
let arr1 = [1, 2, 3];
let arr2 = [...arr1];
console.log(arr1); // [1, 2, 3]
console.log(arr2); // [1, 2, 3]
console.log(arr1 === arr2); // false
对于期待可迭代对象的构造函数,只要传入一个可迭代对象就可以实现复制:
let map1 = new Map([[1, 2], [3, 4]]);
let map2 = new Map(map1);
console.log(map1); // Map {1 => 2, 3 => 4}
console.log(map2); // Map {1 => 2, 3 => 4}
也可以构建数组的部分元素:
let arr1 = [1, 2, 3];
let arr2 = [0, ...arr1, 4, 5];
console.log(arr2); // [0, 1, 2, 3, 4, 5]
浅复制意味着只会复制对象引用,复制指针,指向的还是同一个对象。
let arr1 = [{}];
let arr2 = [...arr1];
arr1[0].foo = 'bar';
console.log(arr2[0]); // { foo: 'bar' }