2019-7-23
第七章 Set与Map
Set是不包含重复值的列表,一般不会像对待数组那样来访问Set中的某项,更常见的操作是在Set中检查某个值是否存在。
Map则是键与值相对应的值得集合,map的每个项都存储了两块数据,通过键可检索对应的值。
(一)ES5中的Set和Map
- 在ES5中,使用对象属性来模拟Set和Map
let set = Object.create(null);
set.foo = true;
// 检查属性的存在性
if (set.foo) {
// do something
}
使用对象模拟Set与模拟Map之间唯一实质区别是所存储的值,与Set仅检查键的存在性不同,Map多数被用来提取数据。
let map = Object.create(null);
map.foo = "bar";
// 提取一个值
let value = map.foo;
console.log(value); // "bar"
- 变通方法的问题
由于对象属性的类型必须为字符串,必须保证任意两个键不能被转换为相同的字符串。
问题1:默认转换为字符串引起的问题
数值类型的键会在内部被转换为字符串,若需要将数值和字符串同时作为键来使用,会引起问题
let map = Object.create(null);
map[5] = "foo";
console.log(map["5"]); // "foo"
对象类型的键默认的字符串表示形式都为"[object Object]",不能将对象类型作为键使用。
let map = Object.create(null),
key1 = {},
key2 = {};
map[key1] = "foo";
console.log(map[key2]); // "foo" key1和key2都被转换成字符串"[object Object]"
问题2:键值为假值引起的问题
let map = Object.create(null);
map.count = 1;
// 是检查"count"属性的存在性,还是想检查非零值?
if (map.count) {
// ...
}
Tips: JS存在in运算符,若属性存在于对象中,会返回true而无须读取对象的属性值。不过,in运算符会搜索对象的原型,所有只有让它处理原型为null的对象时才是安全的。
(二)ES6的Set
es6的Set类型是一种无重复值的有序列表。
1. 创建Set并添加项
创建: new Set() 可接受一个可迭代对象作为参数,如数组
添加项: add() 当重复添加相同值时,第一次之后的调用都会被忽略
let set = new Set();
set.add(5);
set.add("5");
console.log(set.size); // 2
Set不会使用强制类型转换。
Set内部使用Object.is()方法来判断两个值是否相等,唯一的例外是+0和-0在Set中被判断为是相等的。
let set = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
console.log(set.size); // 5
size属性:标识Set包含了多少项
has()方法:检验某个值是否存在
let set = new Set();
set.add(5);
set.add("5");
console.log(set.has(5)); // true
console.log(set.has(6)); // false
2. 移除值
delete(): 移除单个值
clear(): 清除所有值
let set = new Set();
set.add(5);
set.add("5");
console.log(set.has(5)); // true
set.delete(5);
console.log(set.has(5)); // false
console.log(set.size); // 1
set.clear();
console.log(set.has("5")); // false
console.log(set.size); // 0
3. Set上的forEach()方法
Set上的forEach()方法的前两个参数相同, 因为Set没有键,所以将Set中的每一项同时认定为键与值。
let set = new Set([1, 2]);
// value 与 key 相同, ownerSet是set自身
set.forEach(function(value, key, ownerSet) {
console.log(key + " " + value);
console.log(ownerSet === set);
});
1 1
true
2 2
true
如果想在回调函数中使用this,可以给forEach()传入一个this值作为第二个参数
let set = new Set([1, 2]);
let processor = {
output(value) {
console.log(value);
},
process(dataSet) {
dataSet.forEach(function(value) {
this.output(value);
}, this);
}
};
processor.process(set);
也可以使用箭头函数来达到相同的效果,而不必传入第二个参数
let set = new Set([1, 2]);
let processor = {
output(value) {
console.log(value);
},
process(dataSet) {
dataSet.forEach((value) => this.output(value));
}
};
processor.process(set);
4. 将Set转换为数组
Set能非常好的追踪值,但无法像数组一样用索引来直接访问某个值,若有需要,可以用扩展运算符(...)将Set转换为数组。
let set = new Set([1, 2, 3, 3, 3, 4, 5]),
array = [...set];
console.log(array); // [1,2,3,4,5]
利用Set和扩展运算符,可以轻松实现数组去重:
function eliminateDuplicates(items) {
return [...new Set(items)];
}
let numbers = [1, 2, 3, 3, 3, 4, 5],
noDuplicates = eliminateDuplicates(numbers);
console.log(noDuplicates); // [1,2,3,4,5]
-
Weak Set
Set类型存储对象引用的方式,被称为Strong Set(强引用的Set),对象存在Set中,相当于存储在变量中,只要对于Set实例的引用存在,所存储的对象就无法被垃圾回收机制回收,从而无法释放内存,会导致内存泄漏。
Weak Set类型只允许存储对象弱引用,不能存储基本类型的值。对象的弱引用在它自己成为该对象的唯一引用时,不会阻止垃圾回收。
应用场景:存Dom元素的引用
Weak Set创建:new WeakSet(),包含add()方法、has()方法、delete()方法,没有size属性。
let set = new WeakSet(),
key = {};
// add the object to the set
set.add(key);
console.log(set.has(key)); // true
set.delete(key);
console.log(set.has(key)); // false
Weak Set 与 Set之间最大的区别是对象的弱引用:
let set = new WeakSet(),
key = {};
// add the object to the set
set.add(key);
console.log(set.has(key)); // true
// remove the last strong reference to key, also removes from weak set
key = null;
Weak Set 与 Set的其他差异:
- 给Weak Set的add()方法传入非对象参数会报错,has()或delete()则会在传入非对象参数的时候返回false;
- 不可迭代,不能用于for-of循环,无法暴露任何迭代器如keys()或values()方法
- 没有forEach()方法;
- 没有size属性
若只想追踪对象的引用,推荐使用Weak Set
(三) ES6的Map
Es6的Map类型是键值对的有序列表。而键与值都可以是任意类型。键的比较采用的是Object.is()。
1. 增删改Map
new Map(): 初始化创建map,可接受参数[[key,value], [key, value]...]
set(key, value): 设置键值对
get(key) 提取对应的值,若无,返回undefined
let map = new Map();
map.set("title", "Understanding ES6");
map.set("year", 2016);
console.log(map.get("title")); // "Understanding ES6"
console.log(map.get("year")); // 2016
has(key): 判断指定的键是否存在于Map中;
delete(key): 移除Map中的键以及对应的值;
clear(): 移除Map中所有的键与值
size属性:指明包含了多少个键值对
let map = new Map();
map.set("name", "Nicholas");
map.set("age", 25);
console.log(map.size); // 2
console.log(map.has("name")); // true
console.log(map.get("name")); // "Nicholas"
console.log(map.has("age")); // true
console.log(map.get("age")); // 25
map.delete("name");
console.log(map.has("name")); // false
console.log(map.get("name")); // undefined
console.log(map.size); // 1
map.clear();
console.log(map.has("name")); // false
console.log(map.get("name")); // undefined
console.log(map.has("age")); // false
console.log(map.get("age")); // undefined
console.log(map.size); // 0
map初始化添加数据:
let map = new Map([["name", "Nicholas"], ["age", 25]]); // 将键存储在数组中,是确保在被添加到Map之前不会被强制转换为其他类型的唯一方法
console.log(map.has("name")); // true
console.log(map.get("name")); // "Nicholas"
console.log(map.has("age")); // true
console.log(map.get("age")); // 25
console.log(map.size); // 2
2. Map上的forEach()方法
let map = new Map([ ["name", "Nicholas"], ["age", 25]]);
// value是下个位置的值, key是value对应的键, ownerMap是Map自身
map.forEach(function(value, key, ownerMap) {
console.log(key + " " + value);
console.log(ownerMap === map);
});
name Nicholas
true
age 25
true
传递给forEach()的回调函数接受每个键值对,按照键值对被添加到Map中的顺序。
可以给forEach()提供第二个参数来指定回调函数中的this值,其行为与Set版本的forEach()一致。
3. Weak Map
Weak Map的键存储对象的弱引用,不会干扰垃圾回收,与Weak Set类似。(注意:只有Weak Map的键才是弱引用,而值不是)
Weak Map拥有的方法:get(), has(), delete()
不存在clear(), forEach()和size属性
应用场景1: 在浏览器中创建一个关联到特定DOM元素的对象。
let map = new WeakMap(),
element = document.querySelector(".element");
map.set(element, "Original");
let value = map.get(element);
console.log(value); // "Original"
// remove the element
element.parentNode.removeChild(element);
element = null;
// the weak map is empty at this point
应用场景2:在对象实例中存储私有数据
let Person = (function() {
let privateData = new WeakMap();
function Person(name) {
privateData.set(this, { name: name });
}
Person.prototype.getName = function() {
return privateData.get(this).name;
};
return Person;
}());
若只想使用对象类型的键, 推荐Weak Map