ES6学习笔记(九)Set和Map数据结构
1.set
基本等于Java的Set集合类型,无序不可重复集,常被用来去重。
基本用法
const s = new Set();//通过Set()构造函数创建 [2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x)); for (let i of s) {
console.log(i);
}
// 2 3 5 4
Set
函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4] // 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // // 例三
const set = new Set(document.querySelectorAll('div'));
set.size // // 类似于
const set = new Set();
document
.querySelectorAll('div')
.forEach(div => set.add(div));
set.size //
上面代码中,例一和例二都是Set
函数接受数组作为参数,例三是接受类似数组的对象作为参数。
上面代码也展示了一种去除数组重复成员的方法。
// 去除数组的重复成员
[...new Set(array)]
上面的方法也可以用于,去除字符串里面的重复字符。
[...new Set('ababbc')].join('')
// "abc"
向 Set 加入值的时候,不会发生类型转换,所以5
和"5"
是两个不同的值。Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,它类似于精确相等运算符(===
),主要的区别是NaN
等于自身,而精确相等运算符认为NaN
不等于自身。
Set 实例的属性和方法
Set 结构的实例有以下属性。
Set.prototype.constructor
:构造函数,默认就是Set
函数。Set.prototype.size
:返回Set
实例的成员总数。
Set 实例的方法分为两大类:操作方法(用于操作数据)和遍历方法(用于遍历成员)。下面先介绍四个操作方法。
add(value)
:添加某个值,返回 Set 结构本身。delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。has(value)
:返回一个布尔值,表示该值是否为Set
的成员。clear()
:清除所有成员,没有返回值。
set遍历
Set 结构的实例有四个遍历方法,可以用于遍历成员。
keys()
:返回键名的遍历器values()
:返回键值的遍历器entries()
:返回键值对的遍历器forEach()
:使用回调函数遍历每个成员
四个方法配合for of使用
需要特别指出的是,Set
的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 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"]
扩展运算符(...
)内部使用for...of
循环,所以也可以用于 Set 结构。
let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['red', 'green', 'blue']
数组的map
和filter
方法也可以间接用于 Set
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6} let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}
因此使用 Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]); // 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4} // 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3} // 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
如果想在遍历操作中,同步改变原来的 Set 结构,目前没有直接的方法,但有两种变通方法。一种是利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构;另一种是利用Array.from
方法。
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6 // 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6
2.map
含义和基本用法
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
const data = {};
const element = document.getElementById('myDiv'); data[element] = 'metadata';
data['[object HTMLDivElement]'] // "metadata"
上面代码原意是将一个 DOM 节点作为对象data
的键,但是由于对象只接受字符串作为键名,所以element
被自动转为字符串[object HTMLDivElement]
。
为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
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
上面代码使用 Map 结构的set
方法,将对象o
当作m
的一个键,然后又使用get
方法读取这个键,接着使用delete
方法删除了这个键。
上面的例子展示了如何向 Map 添加成员。作为构造函数,Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。
const map = new Map([
['name', '张三'],
['title', 'Author']
]); map.size //
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"
Map
构造函数接受数组作为参数,实际上执行的是下面的算法。
const items = [
['name', '张三'],
['title', 'Author']
]; const map = new Map(); items.forEach(
([key, value]) => map.set(key, value)
);
事实上,不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构(详见《Iterator》一章)都可以当作Map
构造函数的参数。这就是说,Set
和Map
都可以用来生成新的 Map。
const set = new Set([
['foo', 1],
['bar', 2]
]);
const m1 = new Map(set);
m1.get('foo') // const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') //
注意,只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。
const map = new Map(); map.set(['a'], 555);
map.get(['a']) // undefined
上面代码的set
和get
方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,因此get
方法无法读取该键,返回undefined
。
同理,同样的值的两个实例,在 Map 结构中被视为两个键。
const map = new Map(); const k1 = ['a'];
const k2 = ['a']; map
.set(k1, 111)
.set(k2, 222); map.get(k1) //
map.get(k2) //
上面代码中,变量k1
和k2
的值是一样的,但是它们在 Map 结构中被视为两个键。
由上可知,Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0
和-0
就是一个键,布尔值true
和字符串true
则是两个不同的键。另外,undefined
和null
也是两个不同的键。虽然NaN
不严格相等于自身,但 Map 将其视为同一个键。
实例的属性和操作方法
(1)size 属性
size
属性返回 Map 结构的成员总数。
(2)set(key, value)
set
方法设置键名key
对应的键值为value
,然后返回整个 Map 结构。如果key
已经有值,则键值会被更新,否则就新生成该键。
set
方法返回的是当前的Map
对象,因此可以采用链式写法。
let map = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
(3)get(key)
(4)has(key)
(5)delete(key)
delete
方法删除某个键,返回true
。如果删除失败,返回false
。
(6)clear()
clear
方法清除所有成员,没有返回值。
遍历方法
Map 结构原生提供三个遍历器生成函数和一个遍历方法。
keys()
:返回键名的遍历器。values()
:返回键值的遍历器。entries()
:返回所有成员的遍历器。forEach()
:遍历 Map 的所有成员。
需要特别注意的是,Map 的遍历顺序就是插入顺序。
const map = new Map([
['F', 'no'],
['T', 'yes'],
]); for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T" for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes" for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes" // 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes" // 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
Map 结构转为数组结构,比较快速的方法是使用扩展运算符(...
)。
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]); [...map.keys()]
// [1, 2, 3] [...map.values()]
// ['one', 'two', 'three'] [...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']] [...map]
// [[1,'one'], [2, 'two'], [3, 'three']]
结合数组的map
方法、filter
方法,可以实现 Map 的遍历和过滤(Map 本身没有map
和filter
方法)。
与其他数据结构的互相转换
(1)Map 转为数组
Map 转为数组最方便的方法,就是使用扩展运算符(...
)。
(2)数组 转为 Map
将数组传入 Map 构造函数,就可以转为 Map。
(3)Map 转为对象
如果所有 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 }
如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。
(4)对象转为 Map
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
} objToStrMap({yes: true, no: false})
// Map {"yes" => true, "no" => false}
(5)Map 转为 JSON
Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。
另一种情况是,Map 的键名有非字符串,这时可以选择转为数组 JSON。
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
} let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}' function mapToArrayJson(map) {
return JSON.stringify([...map]);
} let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
(6)JSON 转为 Map
JSON 转为 Map,正常情况下,所有键名都是字符串。
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
} jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是 Map 转为数组 JSON 的逆操作。
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
} jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
ES6学习笔记(九)Set和Map数据结构的更多相关文章
- ES6学习笔记(10)----Set和Map数据结构
参考书<ECMAScript 6入门>http://es6.ruanyifeng.com/ Set和Map数据结构 1.Set 基本用法 Set是一种新的数据结构,它的成员都是唯一 ...
- ES6学习总结之Set和Map数据结构的理解
前言 当我们需要存储一些数据的时候,首先想到的是定义一个变量用来存储,之后我们可能学了数组,发现数组比变量可以存储更多的数据,接着可能有其它的存储数据的方法等等,然而我今天需要介绍的是在ES6中比较常 ...
- ES6 学习笔记(十)Map的基本用法
1 基本用法 Map类型是键值对的有序列表,而键和值都可以是任意类型.可以看做Python中的字典(Dictionary)类型. 1.1 创建方法 Map本身是一个构造函数,用来生成Map实例,如: ...
- ES6学习笔记九:修饰器
一:修饰器(Decorator)是一个函数,用来修改类的行为. 1)定义与使用 function 修饰器名(target) { //target是被修饰对象,可用target.xxx进行调用修改 } ...
- 多线程学习笔记九之ThreadLocal
目录 多线程学习笔记九之ThreadLocal 简介 类结构 源码分析 ThreadLocalMap set(T value) get() remove() 为什么ThreadLocalMap的键是W ...
- JS&ES6学习笔记(持续更新)
ES6学习笔记(2019.7.29) 目录 ES6学习笔记(2019.7.29) let和const let let 基本用法 let 不存在变量提升 暂时性死区 不允许重复声明 块级作用域 级作用域 ...
- ES6学习笔记之变量的解构赋值
变量的解构赋值 ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构. 数组的解构赋值 以前,为变量赋值,只能直接指定值: 1 2 3 var a = 1; var b = 2; ...
- es6学习笔记-class之一概念
前段时间复习了面向对象这一部分,其中提到在es6之前,Javasript是没有类的概念的,只从es6之后出现了类的概念和继承.于是乎,花时间学习一下class. 简介 JavaScript 语言中,生 ...
- ES6学习笔记<五> Module的操作——import、export、as
import export 这两个家伙对应的就是es6自己的 module功能. 我们之前写的Javascript一直都没有模块化的体系,无法将一个庞大的js工程拆分成一个个功能相对独立但相互依赖的小 ...
- ES6学习笔记<四> default、rest、Multi-line Strings
default 参数默认值 在实际开发 有时需要给一些参数默认值. 在ES6之前一般都这么处理参数默认值 function add(val_1,val_2){ val_1 = val_1 || 10; ...
随机推荐
- mysql-计算字段
一.计算字段 存储在数据库表中的数据一般不是应用程序所需要的格式 1.如果想在一个字段中既显示公司名,又显示公司的地址,但这两个信息一般包含在不同的字段中. 2.城市.州和邮编存储在不同的列中,但邮件 ...
- 【android】解决Viewpager设置高度为wrap_content无效的方法
今天发现设置viewpager高度为wrap_content时并没作用.stackoverflow给出了解决方式,就是自己定义viewpager,重写onMesure()方法: public clas ...
- How to Download Windows 10 Spotlight/Lock Screen Images
http://www.online-tech-tips.com/windows-10/download-windows-10-spotlight-lock-screen-images/ 图片位置 C: ...
- 集群环境下,Session管理的几种手段
集群环境下,Session管理的几种手段 1.Session复制 缺点:集群服务器间需要大量的通信进行Session复制,占用服务器和网络的大量资源. 由于所有用户的Session信息在每台服务器上都 ...
- zzulioj--1776--和尚特烦恼2——第几个素数(技巧模拟)
1776: 和尚特烦恼2--第几个素数 Time Limit: 1 Sec Memory Limit: 128 MB Submit: 267 Solved: 100 SubmitStatusWeb ...
- struts2标签#、%、$取值
转自:https://blog.csdn.net/kosum/article/details/21375635 首先了解下OGNL的概念: OGNL是Object-Graph Navigation L ...
- Saying Good-bye to Cambridge Again
Saying Good-bye to Cambridge Again Very quietly I take my leave, As quietly as I came here; ...
- RelativeLayout中的baseline
比如,加入两个相邻的TextView,给第二个TextView一个大一点的padding(比如20dp),如果加了layout_alignBaseline到第二个TextView中的话, TextVi ...
- Spring SpEL in JSP and Assign SpEL value to Java variable in JSP
Spring SpEL in JSP and Assign SpEL value to Java variable in JSP method 1 use----ServletContextAttri ...
- EasyUI 之 DataGrid的两种赋值方法
方法一:使用ViewData赋值 首先,我们创建一个User的实体类 public class User { public string UserID; public string UserName; ...