七: ES6 Set Map
前言
该部分为书籍 深入理解ES6 第七章(Set与Map)笔记
ES5中的 Set 与 Map
在 ES5 中, 一般使用对象属性来模拟 Set 与 Map
let set = Object.create(null);
set.foo = true;
// 检查属性的存在性
if (set.foo) {
// 一些操作
}
使用对象模拟 Map 与 Set 之间唯一真正的区别是所存储的值
与 Set 不同, Map 多数被用来提取数据,而不是仅检查键的存在性。
let map = Object.create(null);
map.foo = "bar";
// 提取一个值
let value = map.foo;
console.log(value); // "bar"
变通方法的问题
由于对象属性的类型必须为字符串,你就必须保证任意两个键不能被转换为相同的字符串。
let map = Object.create(null);
map[5] = "foo";
console.log(map["5"]); // "foo"
若使用对象作为键, 就会出现另一个问题(对象会被转换为字符串[object Object])
ES6的 Set
ES6 新增了 Set 类型, 这是一种无重复值的有序列表.(Set 不是一种基础类型, 还是 Object 类型之上封装的一种数据结构, [[class]] 属性为 "Set")
Set 允许对它包含的数据进行快速访问,从而增加了一个追踪离散值的更有效方式。
1. 创建 Set
使用 new Set()来创建
在 Set 内部的比较使用了 Object.is() 方法, 来判断两个值是否相等, 唯一的例外是 +0 与-0 在 Set 中被判断为是相等的
// 参数: iterable
// 如果传递一个可迭代对象,它的所有元素将不重复地被添加到新的 Set中。如果不指定此参数或其值为null,则新的 Set为空。
let set = new Set([iterable]?);
2. 方法以及属性
Set.prototype.add(value): 添加项目
如果 add() 方法用相同值进行了多次调用,那么在第一次之后的调用实际上会被忽略
set.add(5);
Set.prototype.delete(value): 删除值
移除Set的中与这个值相等的元素,返回Set.prototype.has(value)在这个操作前会返回的值(即如果该元素存在,返回true,否则返回false)。Set.prototype.has(value)在此后会返回false。
Set.prototype.clear():删除所有值
Set.prototype.has(value): 判断值是否存在
Set.prototype.size: 属性, 返回Set对象的值的个数。
3. 迭代方法
Set.prototype.forEach(callbackFn[, thisArg])
因为 Set 并没有数组或者对象概念的索引或键, 为了与其他数据结构的 forEach 保持一致, 回调函数的第一个与第二个参数是相同的
要记住,虽然 Set 能非常好地追踪值,并且 forEach() 可以让你按顺序处理每一项,但是却无法像数组那样用索引来直接访问某个值。
let set = new Set([1, 2]);
/*
* 回调函数接受三个参数:
* Set 中下个位置的值
* 与第一个参数相同的值
* 目标 Set 自身
*/
set.forEach(function(value, key, ownerSet) {
console.log(key + " " + value);
console.log(ownerSet === set);
});
Set.prototype.keys()
与values()方法相同,返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。
Set.prototype.values()
返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值。
Set.prototype.entries()
返回一个新的迭代器对象,该对象包含Set对象中的按插入顺序排列的所有元素的值的[value, value]数组。为了使这个方法和Map对象保持相似, 每个值的键和值相等。
4.将 Set 转化为数组
数组转 Set
直接将数组传递给 Set 构造器
Set转 数组
使用 扩展运算符
let set = new Set([1, 2, 3, 3, 3, 4, 5]),
array = [...set];
console.log(array); // [1,2,3,4,5]
可以用这个来快速进行数组去重
function eliminateDuplicates(items) {
return [...new Set(items)];
}
ES6中 Weak Set
由于 Set 类型存储对象引用的方式,它也可以被称为 Strong Set 。对象存储在 Set 的一个实例中时,实际上相当于把对象存储在变量中。只要对于 Set 实例的引用仍然存在,所存储的对象就无法被垃圾回收机制回收,从而无法释放内存。
let set = new Set(),
key = {};
set.add(key);
console.log(set.size); // 1
// 取消原始引用
// 将 key 设置为 null 清除了对 key 对象的一个引用,但是另一个引用还存于set 内部
key = null;
console.log(set.size); // 1
// 重新获得原始引用
key = [...set][0];
为了缓解这个问题, ES6 也包含了 Weak Set ,该类型只允许存储对象弱引用,而不能存储基本类型的值。对象的弱引用在它自己成为该对象的唯一引用时,不会阻止垃圾回收
1. 创建 Weak Set
使用 new WeakSet()构造器
**注意: ** Weak Set 项不能存在非对象的值, 如果传入了非对象的值, 就会抛出错误
// [iterable]: 如果传入一个可迭代对象作为参数, 则该对象的所有迭代值都会被自动添加进生成的 WeakSet 对象中。null 被认为是 undefined。
let set = new WeakSet([iterable])
方法
WeakSet.prototype.add(value): 添加项
WeakSet.prototype.has(value): 判断项
WeakSet.prototype.delete(value): 删除项
2. 与 Set 类型的差异
Weak Set 看起来功能有限,而这对于正确管理内存而言是必要的。一般来说,若只想追踪对象的引用,应当使用 Weak Set 而不是正规 Set 。
最大区别是对象的弱引用(即当其他对象没有引用时, 就会被环境所回收)
JS 引擎垃圾回收机制最常见的就是标记清除法(可参考JavaScript 高级程序设计书)
对于 WeakSet 的实例,若调用 add() 方法时传入了非对象的参数,就会抛出错误(has() 或 delete() 则会在传入了非对象的参数时返回 false );
Weak Set 不可迭代,因此不能被用在 for-of 循环中;
Weak Set 无法暴露出任何迭代器(例如 keys() 与 values() 方法),因此没有任何编程手段可用于判断 Weak Set 的内容;
Weak Set 没有 forEach() 方法;
Weak Set 没有 size 属性。
ES6中的Map
ES6的 Map 类型是键值对的有序列表, 而键和值都可以是任意类型 . 键的比较使用的是 Object.is(), 因此 5与 "5" 同时作为键, 因为它们类型不同.
1. 创建 Map
new Map([iterable])
在方法内部使用的是 Object.is() , 与 new Set() 类似
// Iterable 可以是一个数组或者其他 iterable 对象,其元素为键值对(两个元素的数组,例如: [[ 1, 'one' ],[ 2, 'two' ]])。 每个键值对都会添加到新的 Map。null 会被当做 undefined。
let set = new Set([iterable]?);
2. 方法 和 属性
Map.prototype.size: 返回Map对象的键/值对的数量。
Map.prototype.get(key): 返回键对应的值,如果不存在,则返回undefined。
Map.prototype.set(key, value): 设置Map对象中键的值。返回该Map对象。
Map.prototype.has(key): 返回一个布尔值,表示Map实例是否包含键对应的值。
Map.prototype.delete(key): 删除项
如果 Map 对象中存在该元素,则移除它并返回 true;否则如果该元素不存在则返回 false。随后调用 Map.prototype.has(key) 将返回 false 。
Map.prototype.clear(): 移除Map对象的所有键/值对 。
3. 迭代方法
Map.prototype.forEach(callbackFn[, thisArg]): 按插入顺序,为 Map对象里的每一键值对调用一次callbackFn函数。如果为forEach提供了thisArg,它将在每次回调中作为this值。
Map.prototype.keys(): 返回一个新的 Iterator对象, 它按插入顺序包含了Map对象中每个元素的键 。
Map.prototype.values():返回一个新的Iterator对象,它按插入顺序包含了Map对象中每个元素的值 。
Map.prototype.entries(): 返回一个新的 Iterator 对象,它按插入顺序包含了Map对象中每个元素的 [key, value] 数组。
4. 与普通对象(Object)的区别
一个Object的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值,包括函数、对象、基本类型。
Map 中的键值是有序的,而添加到对象中的键则不是。因此,当对它进行遍历时,Map 对象是按插入的顺序返回键值。
注意:自ECMAScript 2015规范以来,对象确实保留了字符串和Symbol键的创建顺序; 因此,在只有字符串键的对象上进行迭代将按插入顺序产生键。**
你可以通过 size 属性直接获取一个 Map 的键值对个数,而 Object 的键值对个数只能手动计算。
Map 可直接进行迭代,而 Object 的迭代需要先获取它的键数组,然后再进行迭代。
Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
注意:虽然 ES5 开始可以用 map = Object.create(null) 来创建一个没有原型的对象,但是这种用法不太常见
Map 在涉及频繁增删键值对的场景下会有些性能优势。
ES6中的 Weak Map
Weak Map 对 Map 而言,就像 Weak Set 对 Set 一样: Weak 版本都是存储对象弱引用的方式。在 Weak Map 中,所有的键都必须是对象(尝试使用非对象的键会抛出错误),而且这些对象都是弱引用,不会干扰垃圾回收。当 Weak Map 中的键在 Weak Map 之外不存在引用时,该键值对会被移除。
必须注意的是, Weak Map 的键才是弱引用,而值不是。在 Weak Map 的值中存储对象会阻止垃圾回收,即使该对象的其他引用已全都被移除。
1. 创建 Weak Map
使用 new WeakMap() 构造器
键必须是非空的对象, 否则会抛出错误
值则允许是任意类型
// Iterable 是一个数组(二元数组)或者其他可迭代的且其元素是键值对的对象。每个键值对会被加到新的 WeakMap 里。null 会被当做 undefined。
new WeakMap([iterable]?)
方法
WeakMap.prototype.set(key, value): 在WeakMap中设置一组key关联对象,返回这个 WeakMap对象。
WeakMap.prototype.get(key): 返回key关联对象, 或者 undefined(没有key关联对象时)。
WeakMap.prototype.has(key): 根据是否有key关联对象返回一个Boolean值。
WeakMap.prototype.delete(key): 移除key的关联对象。执行后 WeakMap.prototype.has(key)返回false。
2. Weak Map 的用法与局限性
当决定是要使用 Weak Map 还是使用正规 Map 时,首要考虑因素在于你是否只想使用对象类型的键。如果你打算这么做,那么最好的选择就是 Weak Map 。因为它能确保额外数据在不再可用后被销毁,从而能优化内存使用并规避内存泄漏。
Weak Map 值为他们的内容提供了很小的可见度, 因此你不能使用 forEach() 方法、size 属性 或 clear() 方法来管理其中的项
发表评论 (审核通过后显示评论):