《Understanding ES6》chapter7 Sets and Maps

238 阅读6分钟

2019-7-23

第七章 Set与Map

Set是不包含重复值的列表,一般不会像对待数组那样来访问Set中的某项,更常见的操作是在Set中检查某个值是否存在。

Map则是键与值相对应的值得集合,map的每个项都存储了两块数据,通过键可检索对应的值。

(一)ES5中的Set和Map

  1. 在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. 变通方法的问题

由于对象属性的类型必须为字符串,必须保证任意两个键不能被转换为相同的字符串。

问题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]
  1. 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