ECMAScript 6之Set 和 Map 数据结构

1.Set

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

Set本身是一个构造函数,用来生成 Set 数据结构。

let s = new Set();

typeof s; // "object"

Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

// 接受数组作为参数
let x = new Set(["jidi", "rycony", "xuxiake2019"]);
x; // Set(3) {"jidi", "rycony", "xuxiake2019"}

// 接受具有iterable接口的数据结构作为参数
let y = new Set("jidi");
y; // Set(3) {"j", "i", "d"}

上面代码中,变量y最终只有三个元素,那是因为Set数据结构成员的值都是唯一的。

向 Set 加入值的时候,不会发生类型转换。Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”。

let x = new Set();

// set内部认为NaN等于NaN
x.add(NaN);
x.add(NaN);
x; // Set(1) {NaN}

// set添加值,不会发生类型转换
x.add(1);
x.add("1");
x; // Set(3) {NaN, 1, "1"}

// set内部+0等于-0
x.add(+0);
x.add(-0);
x; // Set(4) {NaN, 1, "1", 0}

1.1 Set 实例的属性和方法

Set结构的实例有以下属性:

  • Set.prototype.constructor:构造函数,默认是Set函数。
  • Set.prototype.size:返回Set实例的成员总数。
let x = new Set([1, 2, 3]);

// 获得实例构造函数,默认为Set
x.constructor; // ƒ Set() { [native code] }
// 获得set实例的成员数
x.size; // 3

Set结构的实例有以下方法:

操作数据相关方法

  • Set.prototype.add(val):添加某个值,返回 Set 结构本身。
  • Set.prototype.delete(val):删除某个值,返回一个布尔值,表示删除是否成功。
  • Set.prototype.has(val):返回一个布尔值,表示该值是否为Set的成员。
  • Set.prototype.clear():清除所有成员,没有返回值。
let x = new Set();

// 向set添加值,返回set结构
x.add("jidi");
x.add("rycony");
x; // Set(2) {"jidi", "rycony"}

// 删除,返回布尔值,表示是否删除成功
x.delete("jidi"); // true
x; // Set(1) {"rycony"}

// 判断一个值是否在set中
x.has("rycony"); // true

// 清空set
x.clear();
x; // Set(0) {}

遍历数据相关方法

  • Set.prototype.keys():返回键名的遍历器。
  • Set.prototype.values():返回键值的遍历器。
  • Set.prototype.entries():返回键值对的遍历器。
  • Set.prototype.forEach():使用回调函数遍历每个成员。

(1)keys(),values(),entries()
keys()values()entries()三个方法返回的都是遍历器对象。由于Set结构没有键名,只有键值,keys()values()两个方法的行为一致。

let x = new Set("jidi");

// 获取键名
x.keys(); // SetIterator {"j", "i", "d"}

// 获取键值
x.values(); // SetIterator {"j", "i", "d"}

//获取键值对
x.entries(); // SetIterator {"j" => "j", "i" => "i", "d" => "d"}

Set结构的实例默认可遍历,它的默认遍历器生成函数就是values()方法。

Set.prototype[Symbol.iterator] === Set.prototype.values; // true

由于Set结构实例默认遍历器生成函数就是values()方法,我们可以省略values()方法,遍历Set结构实例。

let x = new Set("jidi");

for(let m of x.values()) {
	console.log(m);
}
// 等价于
for(let m of x) {
	console.log(m);
}

(2) forEach()
Set 结构的实例拥有forEach方法,用于对每个成员执行某种操作,没有返回值。

let x = new Set([1, 2, 3, 4]);
let y = [];

x.forEach((value,key) => y.push(value * key));
y; // [1, 4, 9, 16]

上面代码中,forEach()方法的参数是一个函数。该函数可以接受三个参数,依次是:键值,键名,集合本身。

forEach()方法还可以有第二个参数,用来绑定处理函数内部的this对象。

1.2 Set 实例与扩展运算符结合使用

扩展运算符(...)内部使用的是for ...of循环,也可以用于Set结构实例。

let x = new Set(["jidi", "rycony", "xuxiake2016"]);

// 通过扩展运算符将Set结构实例转为数组
[...x]; // ["jidi", "rycony", "xuxiake2016"]

扩展运算符(...)与Set结合使用,可以去除数组的重复成员。

let x = [1, 2, 3, 4, 3, 2];

x = [...new Set(x)]; // [1, 2, 3, 4]

通过扩展运算符,数组的mapfilter方法也能间接被Set结构实例使用。

let x = [1, 2, 3, 4, 5, 6];
let set = new Set(x);

// 使用数组的map方法
set = new Set([...set].map(value => value + 1)); // Set(6) {2, 3, 4, 5, 6, 7}

// 使用数组的filter方法
set = new Set([...set].filter(x => x % 2 === 0 )); // Set(3) {2, 4, 6}

Set结构实例与扩展运算符结合使用,可以很容易实现并集,交集,差集。

let a = new Set([1, 2, 3, 4]);
let b = new Set([4, 3, 2, 9]);

// 并集
let x = new Set([...a, ...b]); // Set(5) {1, 2, 3, 4, 9}

// 交集
let y = new Set([...a].filter(value => b.has(value))); // Set(3) {2, 3, 4}

// 差集
let z = new Set([...a].filter(value => !b.has(value))); // Set(1) {1}

如果要在遍历操作中,同步改变原来的 Set 结构,目前有两种变通方法:

  • 利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构
  • 利用Array.from方法,将一个类似数组的结构变为真正的数组,作为Set的参数
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x + 1)); // Set(3) {2, 3, 4}

// 方法二
set = new Set([1, 2, 3]);
set = new Set(Array.from(set, x => x * x)); // Set(3) {1, 4, 9}

2. WeakSet

WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别:

  • WeakSet 的成员只能是对象,而不能是其他类型的值
  • WeakSet 中的对象都是弱引用
let weakSet = new WeakSet();

// WeakSet成员只能是对象
weakSet.add(1); // Uncaught TypeError: Invalid value used in weak set at WeakSet.add
weakSet.add(true); //  TypeError

weakSet.add({name: "jidi"}); // 成功添加对象

WeakSet 是一个构造函数,可以使用new命令,创建 WeakSet 数据结构。

作为构造函数,WeakSet 可以接受一个数组或类似数组的对象作为参数。(具有 Iterator 接口的对象,都可以作为 WeakSet 的参数。)该数组的所有成员,都会自动成为 WeakSet 实例对象的成员。

let a = [[1, 2], [3, 4]];
let weakSet = new WeakSet(a); //  WeakSet {[1, 2], [3, 4]}

上面代码中,数组a的成员也是数组,将a作为WeakSet的参数,a的成员会自动成为WeakSet实例的成员。这意味着,数组或类似数组的对象作为参数时,其内部成员必须是对象

let a = [1, 2];
let weakSet = new WeakSet(a); // Uncaught TypeError: Invalid value used in weak set at WeakSet.add at new WeakSet

2.1 WeakSet实例方法

WeakSet 结构有以下三个方法:

  • WeakSet.prototype.add(obj):向 WeakSet 实例添加一个新成员
  • WeakSet.prototype.delete(obj):清除 WeakSet 实例的指定成员
  • WeakSet.prototype.has(obj):返回一个布尔值,表示某个值是否在 WeakSet 实例之中
let x = { name: "jidi" };
let y = { name: "rycony" };
let z = { name: "xuxaike2016" };

let weakSet = new WeakSet();

// 添加成员
weakSet.add(x);
weakSet.add(y);
weakSet.add(z);

// 删除成员
weakSet.delete(x); // true

// 判断某个值是否在WeakSet
weakSet.has(x); // false

WeakSet 不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在。而且WeakSet 也没有size属性。

3. Map

JavaScript 的对象(Object),本质上是键值对的集合,但是传统上只能用字符串当作键。
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值都可以当作键。

Map本身是一个构造函数,用来生成 Map数据结构。

let map = new Map();

作为构造函数,Map也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。

let map = new Map([
  ["name", "jidi"]
]);

map.size; // 1

任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数。

let set = new Set([["name", "jidi"], ["age", 22]]);

let map = new Map(set); // Map(2) {"name" => "jidi", "age" => 22}

如果对同一个键多次赋值,后面的值将覆盖前面的值。

let set = new Set([["name", "jidi"], ["name", 22]]);
let map = new Map(set);  // Map(1) {"name" => 22}

如果读取一个未知的键,则返回undefined

new Map().get("name"); // undefined

只有对同一个对象的引用,Map 结构才将其视为同一个键。

let a = { name: "jidi" };
let map = new Map();

// 键名为基本数据类型
map.set("name", "jidi"); // Map(1) {"name" => "jidi"}
map.get("name"); // "jidi"

// 键名为对象
map.set(a, 22);
map.get({name: "jidi"}); // undefined
map.get(a); // 22

上面代码中,键名为基本数据类型时,只要两个值相等,则Map将其视为一个键;当键名为对象时,内存地址一样才会视为一个键。

3.1 Map实例属性和方法

Map结构实例有以下属性:

  • Map.prototype.size:返回Map实例的成员总数
new Map([[name, "jidi"]]).size; // 1

Map结构的实例有以下方法:

操作数据相关方法

  • Map.prototype.set(key, value):设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。
  • Map.prototype.get(key):读取key对应的键值,如果找不到key,返回undefined
  • Map.prototype.has(key):返回一个布尔值,表示某个键是否在当前 Map对象之中。
  • Map.prototype.delete(key):删除某个键,删除成功返回true。如果删除失败,返回false
  • Map.prototype.clear():清除所有成员,没有返回值。
let map = new Map();

map.set('name', "jidi"); // 键是字符串
map.set(22, "age"); // 键是数值
map.set(undefined, ''); // 键是 undefined
map: // Map(3) {"name" => "jidi", 22 => "age", undefined => ""}

map.get(22); // 键名存在,返回键值"age"
map.get(23);  // 键名不存在,返回undefined

map.has(22); // true
map.has(23); // false

map.delete(22); // 删除成功返回true
map; // Map(2) {"name" => "jidi", undefined => ""}

map.clear(); // 清除所有成员
map; // Map(0) {}

遍历数据相关方法

  • Map.prototype.keys():返回键名的遍历器。
  • Map.prototype.values():返回键值的遍历器。
  • Map.prototype.entries():返回所有成员的遍历器。
  • Map.prototype.forEach():遍历 Map 的所有成员。
let map = new Map([
  ["name", "jidi"],
  ["age",  22],
]);

// 遍历键名
for (let key of map.keys()) {
  console.log(key);
}
// "name" "age"

// 遍历键值
for (let value of map.values()) {
  console.log(value);
}
// "jidi" 22

// 遍历键值对
for (let item of map.entries()) {
  console.log(item[0], item[1]);
}
// "name" "jidi"
// "age" 22

// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
}
// "name" "jidi"
// "age" 22

// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
}
// "name" "jidi"
// "age" 22

上面代码中,最后一个例子,表明Map 结构的默认遍历器接口其实就是entries方法。

Map[Symbol.iterator] === Map.entries; // true

4. WeakMap

WeakMap结构与Map结构类似,也是用于生成键值对的集合。但是,它与 Map有两个区别:

  • WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
  • WeakMap的键名所引用的对象都是弱引用。
// WeakMap 可以使用 set 方法添加成员
let weakMap = new WeakMap();
let x = { name: "jidi"};
weakMap.set(x, 22);
weakMap.get(x); // 22

// WeakMap 也可以接受一个数组,作为构造函数的参数
let a1 = [1, 2, 3];
let a2 = [4, 5, 6];
weakMap = new WeakMap([[a1, "jidi"], [a2, "rycony"]]);
weakMap.get(a2); // "rycony"

// 键名必须是对象
map = new WeakMap();
map.set(1, 2); // TypeError: 1 is not an object!
map.set(Symbol(), 2); // TypeError: Invalid value used as weak map key
map.set(null, 2); // TypeError: Invalid value used as weak map key

4.1 WeakMap实例方法

WeakMap 与 Map 在 API 上的区别主要是两个:没有遍历操作;也没有size属性。 因此,WeakMap只有四个方法可用:

  • WeakMap.prototype.get(key):读取key对应的键值,如果找不到key,返回undefined
  • WeakMap.prototype.has(key):返回一个布尔值,表示某个键是否在当前 Map对象之中。
  • WeakMap.prototype.delete(key):删除某个键,删除成功返回true。如果删除失败,返回false
  • WeakMap.prototype.set(key, value):设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。

5. 参考链接

本篇博文是我自己学习笔记,原文请参考:ECMAScript 6 入门
如有问题,请及时指出!
欢迎沟通交流,邮箱:jidi_jidi@163.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值