阮一峰es6要点总结——Set与Map数据结构

笔记,完全摘录自阮一峰

set数据结构

ES6提供的,新的数据结构,类似于数组。

特点:成员值唯一,没有重复的值

  • 生成set数据结构
    使用Set()构造函数,可接受一个数组,类数组对象作为参数。

  • Set实例的方法和属性
    属性

  • Set.prototype.constructor:指向构造函数Set

  • Set.prototype.size:返回set实例的成员总数

方法——操作方法(操作数据)/遍历方法(遍历成员)

  • 操作方法
    • add(value):添加value,并返回Set结构本身
    • delete(value):删除value,返回布尔值,表示是否删除成功
    • has(value):返回布尔值,表示value是否为set的成员
    • clear():清除所有成员
  • 遍历操作
  • key():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员

Set的遍历顺序就是插入顺序

由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。

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

  • Set内部的值相等判断
    使用“Same-value equality”算法,类似全等===,区别是,NaN等于自身。

  • 应用

1.利用Set结构的独特特性,实现数组去重

// 去除数组的重复成员
[...new Set(array)]

2.实现并集、交集、差集

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

3.在遍历中,改变原Set结构

// 方法一,利用原Set结构映射出一个新的结构,然后赋值给原来的Set结构
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6
// 方法二,利用`Array.from()`
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6

WeakSet

特点

  • 成员唯一,且必须是对象。对象均为弱引用

  • 弱引用性:如果没有其他对象引用WeakSet中的对象,那么垃圾回收机制会自动回收该对象占用的内存。

  • 由于弱引用性,导致WeakSet的成员不可遍历(因为无法确保遍历成员的存在)。

用法

构造函数WeakSet(),可传入数组,或类数组等具有iterable接口的对象。

注意:成为WeakSet的成员的是参数的成员,而不是参数本身,所以

var b = [3, 4];
var ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(…)

数组b的成员是数字,不符合WeakSet的特性,会报错。其他情况的参数也一样。

方法

  • WeakSet.prototype.add(value):添加新成员
  • WeakSet.prototype.delete(value):清除指定成员
  • WeakSet.prototype.has(value):返回布尔值,表示某个值是否存在。

应用
存储dom节点,不用担心,这些节点从文档移除是,造成内存泄漏

举例:闭包保存下一个dom节点,当dom节点删除时候,由于还存在引用,无法被回收。使用WeakSet就可以解决这个问题。

另一个例子

const foos = new WeakSet()
class Foo {
  constructor() {
    foos.add(this)
  }
  method () {
    if (!foos.has(this)) {
      throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!');
    }
  }

上面代码保证了Foo的实例方法,只能在Foo的实例上调用。这里使用WeakSet的好处是,foos对实例的引用,不会被计入内存回收机制,所以删除实例的时候,不用考虑foos,也不会出现内存泄漏。

Map

ObjectMap均为Hash结构(键值对集合)

区别
Object键名只能是字符串形式,Map的键名可以是任意类型(包括对象)。

总的来说,Map是一种更完善的Hash结构的实现。如果你需要“键值对”的数据结构,Map比Object更合适。

构造函数
可传入一个数组参数。



var items = [
  ['name', '张三'],
  ['title', 'Author']
];

var map = new Map(items) //等价于下边的

var map = new Map();
items.forEach(([key, value]) => map.set(key, value));

特性

  • Map键名可以是任意类型
  • Map键名为对象类型时,与内存地址绑定,如果内存地址不同,就视为两个键
var map = new Map();

 map.set(['a'], 555);
 map.get(['a']) // undefined
 
 var map = new Map();

 var k1 = ['a'];
 var k2 = ['a'];

 map
.set(k1, 111)
.set(k2, 222);

 map.get(k1) // 111
 map.get(k2) // 222
  • Map键名为简单类型(数值,字符串,布尔值),只要两个值严格相等(===),就认为是一个键。
  • NaN被视为同一个键,0-0被视为同一个键
  • 相同键多次赋值,后覆盖前

实例的属性和方法

  • size属性
    返回Map成员总数

  • set(key, value)
    设置键值对,返回Map本身,所以可以链式

  • get(key)
    读取key对应的值,找不到返回undefined

  • has(key)
    返回布尔值,表示是否存在

  • delete(key)
    删除某键,返回布尔值,表示是否成功删除

  • clear()
    清空所有成员,无返回值。

  • 遍历方法

  • keys()

  • values()

  • entries()

  • forEach(),接受第二个参数,绑定回调函数的this

需要特别注意的是,Map的遍历顺序就是插入顺序。
基本同Set
Map结构的默认遍历器接口(Symbol.iterator属性),就是entries方法。

与其他数据结构的互相转换

  • Map-->Array
    使用扩展运算符(...)
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
  • Array-->Map
new Map([[true, 7], [{foo: 3}, ['abc']]])
// Map {true => 7, Object {foo: 3} => ['abc']}
  • Map-->Object
    键名转成字符串就行
function strMapToObj(strMap) {
  let obj = Object.create(null);
  for (let [k,v] of strMap) {
    obj[k] = v;
  }
  return obj;
}

 let myMap = new Map().set('yes', true).set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
  • Object-->Map
function objToStrMap(obj) {
 let strMap = new Map();
 for (let k of Object.keys(obj)) {
   strMap.set(k, obj[k]);
 }
 return strMap;
}
objToStrMap({yes: true, no: false})
// [ [ 'yes', true ], [ 'no', false ] ]
  • Map-->JSON

两种情况
一.键名全是字符串的,调用上边的,Map-->Object

function strMapToJson(strMap) {
  return JSON.stringify(strMapToObj(strMap));
}

let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'

二.键名存在非字符串的,调用上边的Map-->Array

function mapToArrayJson(map) {
  return JSON.stringify([...map]);
}

let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
  • JSON-->Map

一.正常情况

function jsonToStrMap(jsonStr) {
  return objToStrMap(JSON.parse(jsonStr));
}

jsonToStrMap('{"yes":true,"no":false}')
// Map {'yes' => true, 'no' => false}

二.特殊情况,整个json就是一个数组,每项都是两成员数组

unction jsonToMap(jsonStr) {
  return new Map(JSON.parse(jsonStr));
}

jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}

WeakMap

WeakMap结构与Map结构基本类似,唯一的区别是它只接受对象作为键名(null除外),不接受其他类型的值作为键名,而且键名所指向的对象,不计入垃圾回收机制

WeakMap的设计目的在于,键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),所以其所对应的对象可能会被自动回收。当对象被回收后,WeakMap自动移除对应的键值对。典型应用是,一个对应DOM元素的WeakMap结构,当某个DOM元素被清除,其所对应的WeakMap记录就会自动被移除。基本上,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。

一个例子

var wm = new WeakMap();
var element = document.querySelector(".element");
wm.set(element, "Original");
wm.get(element) // "Original"
element.parentNode.removeChild(element);
element = null;
wm.get(element) // undefined

与Map的区别

  • 没有遍历操作
  • 没有size
  • 无法清空

这些都是因为WeakMap的键不被计入引用,被垃圾回收机制忽略。

WeakMap的方法

  • get
  • set
  • has
  • delete

WeakMap应用的典型例子

例子一

let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();

myWeakmap.set(myElement, {timesClicked: 0});

myElement.addEventListener('click', function() {
  let logoData = myWeakmap.get(myElement);
  logoData.timesClicked++;
}, false);

上面代码中,myElement是一个 DOM 节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是myElement。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

例子二
部署私有属性

let _counter = new WeakMap();
let _action = new WeakMap();

class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  dec() {
    let counter = _counter.get(this);
    if (counter < 1) return;
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this)();
    }
  }
}

let c = new Countdown(2, () => console.log('DONE'));

c.dec()
c.dec()
// DONE

上面代码中,Countdown类的两个内部属性_counter_action,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容