熟悉JavaScript的都知道,ES6中新增了两种新的数据结构 Set 和 Map。
这里不介绍这两种数据结构的用法,如果不知道怎么使用这两种数据结构。可以看这里。
俗话说,知其然并知其所以然,我们虽然知道这两种数据结构怎么使用,但我们最好能知道它们是怎么实现的。
所以我们就来实现简易版的Set和Map。
Set
Set数据结构也称作集合;类似于数组,但是成员的值都是唯一的,没有重复的值。
我们知道普通的JavaScript对象是{键: 值}对的形式;所以有人觉得也可以将Set数据结构理解成存储{值: 值}对的对象,因为对象是不允许有相同的键。
所以实现Set数据结构也有两种方式:数组或对象。本文采用的是后面一种。
接下来,需要声明一些Set可用的属性和方法(尝试模拟与ES6实现相同的Set类)。
属性:
- size:返回Set所包含元素的数量。
方法:
- has(element):如果元素在Set中,返回true,否则返回false。
- add(element):向Set添加一个新元素。
- delete(element):从Set移除一个元素。
- clear():移除Set中的所有元素。
- values():返回一个包含Set中所有值的数组。
直接上代码:
class Set {
constructor() {
this.items = {};
this.size = 0;
}
has(element) {
return element in this.items;
}
add(element) {
if(! this.has(element)) {
this.items[element] = element;
this.size++;
}
return this;
}
delete(element) {
if (this.has(element)) {
delete this.items[element];
this.size--;
}
return this;
}
clear() {
this.items = {}
this.size = 0;
}
values() {
let values = [];
for(let key in this.items) {
if(this.items.hasOwnProperty(key)) {
values.push(key);
}
}
return values;
}
}
上面简易版的Set只是实现了ES6中Set数据结构的常用方法,Set数据结构中keys方法与values方法完全相同,所以也没有实现。entries、forEach等方法也没有实现。
Map
Map数据结构也称作字典或映射;类似于对象,也是键值对的集合;但普通的Javascript对象(Objec)只能用字符串当作键。这给它的使用带来了很大的限制。
而Map数据结构“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
类似于Set,声明一些Set可用的属性和方法
属性:
- size:返回Map所包含元素的数量。
方法:
- has(key):如果元素在Map中,返回true,否则返回false。
- set(key, value):向Map添加一个新元素。
- delete(key):从Map移除一个元素。
- get(key):从Map查找一个元素。
- clear():移除Map中的所有元素。
- keys():返回一个包含Map中所有键的数组。
- values():返回一个包含Map中所有值的数组。
function defaultToString(key) {
if(key === null) {
return 'NULL';
} else if (key === undefined) {
return 'UNDEFINED'
} else if (Object.prototype.toString.call(key) === '[object Object]' || Object.prototype.toString.call(key) === '[object Array]') {
return JSON.stringify(key);
}
return key.toString();
}
class Map {
constructor() {
this.items = {};
this.size = 0;
}
set(key, value) {
if(!this.has(key)) {
this.items[defaultToString(key)] = value;
this.size++;
}
return this;
}
get(key) {
return this.items[defaultToString(key)];
}
has(key) {
return this.items[defaultToString(key)] !== undefined;
}
delete(key) {
if (this.has(key)) {
delete this.items[key];
this.size--;
}
return this;
}
clear() {
this.items = {}
this.size = 0;
}
keys() {
let keys = [];
for(let key in this.items) {
if(this.has(key)) {
keys.push(key)
}
}
return keys;
}
values() {
let values = [];
for(let key in this.items) {
if(this.has(key)) {
values.push(this.items[key]);
}
}
return values;
}
}
因为Map数据结构的键可以是任意值,所以我们需要先实现一个将其他值转换成字符串的方法(defaultToString)
同上面Set一样;上面简易版的Map只是实现了ES6中Map数据结构的常用方法;entries、forEach等方法也没有实现。
ES6中的WeakMap和WeakSet
除了Set和Map这两种新的数据结构,ES6还增加了它们的弱化版本,WeakSet和WeakMap。
基本上,Map和Set与其弱化版本之间仅有的区别是:
- WeakSet或WeakMap类没有keys和values等遍历方法;
- 只能用对象作为键;且是弱引用对象。 在没有其他引用和该键引用同一对象,这个对象将会被垃圾回收(相应的key则变成无效的),所以,WeakMap 和 WeakSet 是不能被遍历的。
小结
其实知道原理后,自己动手实现一个简易的Set或Map也不难;感兴趣的小伙伴可以自己动手实现一下。