PCDotFan

To be an life & code artisan

ES6 - Set 和 Map 数据结构

JavaScript 0 评

Set 集合

ES6 当中的 Set 和数学概念中的「集合」基本上是一致的。只不过它兼有普通 Array 的性质并且具备以下特点:

  • 唯一:保证集合中各个元素都是不相同的
  • 不发生类型转换5"5" 被视为两种不同元素,0false 也是如此
  • NaN 等于自身:类似于精确相等运算符(===),主要的区别是 NaN 等于自身,而精确相等运算符认为 NaN 不等于自身。(Via Set 和 Map 数据结构
  • 空对象不相等:所有的空对象在集合中都被视为不同的元素

其余的用法基本上和 Array 相同,利用 Set 集合很容易实现数学概念当中的「交并补集」,例如:

还多学到一个知识点:剩余参数的内部实现是 for..of,所以具有 Iterable 接口的对象都能使用剩余参数 ...

// 并集
let a = new Set([2, 3, 4, 5])
let b = new Set([4, 5, 7, 8])
let union = new Set([...a, ...b])
// [2, 3, 4, 5, 7, 8]

// 交集
let a = new Set([2, 3, 4, 5])
let b = new Set([4, 5, 7, 8])
let intersection = new Set([...a].filter(item => b.has(item)))
// [4, 5]

// 补集
let a = new Set([1, 2, 3, 4, 5, 6])
let b = new Set([1, 2, 3])
let c = new Set([...a].filter(item => !b.has(item)))
// [4, 5, 6]

new Set() 当中可传递的不仅仅是数组,而是所有可进行遍历的对象(比如字符串):

// 字母去重
let a = new Set("aaaaabbbcccccddd")
// Set(4) {"a", "b", "c", "d"}

Map

在实现 「键->值」对 时我们通常使用的是 Object,但 Object 的设计更像是实现了 「字符串->值」对 的模式,这意味着键名必须是一个合法的字符串(或是 Symbol)。Map 的设计则更加开放,键值均可为任意类型。

和普通 Object 的用法稍有不同,Map 需要以方法 set()get() 对实例进行操作,例如:

let a = new Map()
let b = { foo: 'bar' }
a.set(b, 'barbarbar')
// 我怎么知道这有什么用呢,反正我是把一个对象和 'barbarbar' 联系起来了
a.get(b)
// 得到 'barbarbar'

其实耐心尝试可以发现:Map 实际上是一个类似于 Hash 结构的存在,还是使用上面的例子,然后这样做会如何呢?

a.get({ foo: 'bar' }) // 提示不存在

这是因为 只有对同一个对象的引用,Map 结构才将其视为同一个键 (Thanks ruanyifeng)。换句话来说,以下的操作会出现的对应结果是:

a.set('a', 'b')
a.get('a') // undefined

const keyA = 'a'
a.set(keyA, 'b')
a.get(keyA) // 'b'

那你可能会问了:a.set('a', 'b') 既然都没有报错,那我要怎么取到值啊? 答:转换成其它形式呗(比如 Array, Object)~

Map 和 Array 之间有些暧昧的关系:

From MDN:Map 与数组的关系Map 的实现方法与构造二维数组有关。

var kvArray = [["key1", "value1"], ["key2", "value2"]];

// 使用常规的Map构造函数可以将一个二维键值对数组转换成一个Map对象
var myMap = new Map(kvArray);

myMap.get("key1"); // 返回值为 "value1"

// 使用Array.from函数可以将一个Map对象转换成一个二维键值对数组
console.log(Array.from(myMap)); // 输出和kvArray相同的数组

// 或者在键或者值的迭代器上使用Array.from,进而得到只含有键或者值的数组
console.log(Array.from(myMap.keys())); // 输出 ["key1", "key2"]