map与weakmap_Map和WeakMap数据结构精讲

本文深入探讨JavaScript中的Map和WeakMap数据结构。Map提供了"值—值"的对应,允许任何类型的值作为键;WeakMap则只接受对象作为键,并且键的引用是弱引用,有助于防止内存泄漏。Map提供了set、get、has、delete等方法,而WeakMap缺少遍历操作和clear方法,适合用于DOM节点存储或部署私有属性等场景。
摘要由CSDN通过智能技术生成

一、Map

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。

1.1、概述

ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应。

例:

const m = new Map();

const o = {p: 'Hello World'};

m.set(o, 'content')

m.get(o) // "content"

m.has(o) // true

m.delete(o) // true

m.has(o) // false

1.2、基本用法

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

const map = new Map([

['name', '张三'],

['title', 'Author']

]);

map.size // 2

map.has('name') // true

map.get('name') // "张三"

map.has('title') // true

map.get('title') // "Author"

Map构造函数接受数组作为参数,实际上执行的是下面的算法。

const items = [

['name', '张三'],

['title', 'Author']

];

const map = new Map();

items.forEach(

([key, value]) => map.set(key, value)

);

不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。

const set = new Set([

['foo', 1],

['bar', 2]

]);

const m1 = new Map(set);

m1.get('foo') // 1

const m2 = new Map([['baz', 3]]);

const m3 = new Map(m2);

m3.get('baz') // 3

一些注意点

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

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

只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。

const map = new Map();

map.set(['a'], 555);

map.get(['a']) // undefined

上面代码的set和get方法,表面是针对同一个键,但实际上这是两个不同的数组实例,内存地址是不一样的,因此get方法无法读取该键,返回undefined。

1.3、实例的属性和方法

1.3.1、Map实例的属性

size 属性

size属性返回 Map 结构的成员总数。

1.3.2、Map实例的操作方法

(1)Map.prototype.set(key, value)

set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。

(2)Map.prototype.get(key)

get方法读取key对应的键值,如果找不到key,返回undefined。

(3)Map.prototype.has(key)

has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。

(4)Map.prototype.delete(key)

delete方法删除某个键,返回true。如果删除失败,返回false。

(5)Map.prototype.clear()

clear方法清除所有成员,没有返回值。

1.3.3、Map实例的遍历方法

Map 结构原生提供三个遍历器生成函数和一个遍历方法。

Map.prototype.keys():返回键名的遍历器。

Map.prototype.values():返回键值的遍历器。

Map.prototype.entries():返回所有成员的遍历器。

Map.prototype.forEach():遍历 Map 的所有成员。

二、WeakMap

2.1、概述

WeakMap结构与Map结构类似,也是用于生成键值对的集合。

2.1.1、WeakMap与Map的区别有两点

1、WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。

2、WeakMap的键名所指向的对象,不计入垃圾回收机制。

这两点与“WeakSet和Set的区别”相同

2.1.2、WeakMap的设计目的

有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。请看下面的例子。

const e1 = document.getElementById('foo');

const e2 = document.getElementById('bar');

const arr = [

[e1, 'foo 元素'],

[e2, 'bar 元素'],

];

上面代码中,e1和e2是两个对象,我们通过arr数组对这两个对象添加一些文字说明。这就形成了arr对e1和e2的引用。

一旦不再需要这两个对象,我们就必须手动删除这个引用,否则垃圾回收机制就不会释放e1和e2占用的内存。

// 不需要 e1 和 e2 的时候

// 必须手动删除引用

arr [0] = null;

arr [1] = null;

上面这样的写法显然很不方便。一旦忘了写,就会造成内存泄露。

WeakMap 就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。

总之,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。

注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。

const wm = new WeakMap();

let key = {};

let obj = {foo: 1};

wm.set(key, obj);

obj = null;

wm.get(key)

// Object {foo: 1}

上面代码中,键值obj是正常引用。所以,即使在 WeakMap 外部消除了obj的引用,WeakMap 内部的引用依然存在。

2.2、WeakMap 的语法

WeakMap只有四个方法可用:get()、set()、has()、delete()。

WeakMap 与 Map 在 API 上的区别主要是两个:

1、没有遍历操作(即没有keys()、values()和entries()方法),也没有size属性。

2、无法清空,即不支持clear方法。

WeakSet 与 Set相比,API也是少了这些方法。原因就是WeakMap和WeakSet都是弱引用。

2.3、WeakMap 的用途

2.3.1、WeakMap 应用的典型场合就是 DOM 节点作为键名

let myWeakmap = new WeakMap();

myWeakmap.set(

document.getElementById('logo'),

{timesClicked: 0})

;

document.getElementById('logo').addEventListener('click', function() {

let logoData = myWeakmap.get(document.getElementById('logo'));

logoData.timesClicked++;

}, false);

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

2.3.1、WeakMap 的另一个用处是部署私有属性

const _counter = new WeakMap();

const _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)();

}

}

}

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

c.dec()

c.dec()

// DONE

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

参考文献

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值