-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
第 4 题:介绍下 Set、Map、WeakSet 和 WeakMap 的区别? #6
Comments
Set
|
Set 和 Map 主要的应用场景在于 数据重组 和 数据储存 Set 是一种叫做集合的数据结构,Map 是一种叫做字典的数据结构 1. 集合(Set)ES6 新增的一种新的数据结构,类似于数组,但成员是唯一且无序的,没有重复的值。 Set 本身是一种构造函数,用来生成 Set 数据结构。 new Set([iterable]) 举个例子: const s = new Set()
[1, 2, 3, 4, 3, 2, 1].forEach(x => s.add(x))
for (let i of s) {
console.log(i) // 1 2 3 4
}
// 去重数组的重复对象
let arr = [1, 2, 3, 2, 1, 1]
[... new Set(arr)] // [1, 2, 3] Set 对象允许你储存任何类型的唯一值,无论是原始值或者是对象引用。 向 Set 加入值的时候,不会发生类型转换,所以 let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}
let set1 = new Set()
set1.add(5)
set1.add('5')
console.log([...set1]) // [5, "5"]
2. WeakSetWeakSet 对象允许你将弱引用对象储存在一个集合中 WeakSet 与 Set 的区别:
属性:
方法:
var ws = new WeakSet()
var obj = {}
var foo = {}
ws.add(window)
ws.add(obj)
ws.has(window) // true
ws.has(foo) // false
ws.delete(window) // true
ws.has(window) // false 3. 字典(Map)集合 与 字典 的区别:
const m = new Map()
const o = {p: 'haha'}
m.set(o, 'content')
m.get(o) // content
m.has(o) // true
m.delete(o) // true
m.has(o) // false 任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构都可以当作 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 如果读取一个未知的键,则返回
注意,只有对同一个对象的引用,Map 结构才将其视为同一个键。这一点要非常小心。
上面代码的 由上可知,Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。 如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如
Map 的属性及方法 属性:
操作方法:
遍历方法
const map = new Map([
['name', 'An'],
['des', 'JS']
]);
console.log(map.entries()) // MapIterator {"name" => "An", "des" => "JS"}
console.log(map.keys()) // MapIterator {"name", "des"} Map 结构的默认遍历器接口(
Map 结构转为数组结构,比较快速的方法是使用扩展运算符( 对于 forEach ,看一个例子 const reporter = {
report: function(key, value) {
console.log("Key: %s, Value: %s", key, value);
}
};
let map = new Map([
['name', 'An'],
['des', 'JS']
])
map.forEach(function(value, key, map) {
this.report(key, value);
}, reporter);
// Key: name, Value: An
// Key: des, Value: JS 在这个例子中, forEach 方法的回调函数的 this,就指向 reporter 与其他数据结构的相互转换
4. WeakMapWeakMap 对象是一组键值对的集合,其中的键是弱引用对象,而值可以是任意。 注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。 WeakMap 中,每个键对自己所引用对象的引用都是弱引用,在没有其他引用和该键引用同一对象,这个对象将会被垃圾回收(相应的key则变成无效的),所以,WeakMap 的 key 是不可枚举的。 属性:
方法:
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); 5. 总结
6. 扩展:Object与Set、Map
JS 中的对象(Object),本质上是键值对的集合(hash 结构) const data = {};
const element = document.getElementsByClassName('App');
data[element] = 'metadata';
console.log(data['[object HTMLCollection]']) // "metadata" 但当以一个DOM节点作为对象 data 的键,对象会被自动转化为字符串[Object HTMLCollection],所以说,Object 结构提供了 字符串-值 对应,Map则提供了 值-值 的对应 本文始发于我的博客:Set、WeakSet、Map及WeakMap |
4 Object 转化为 Map , 其中一行代码 map.set(key, obj[k]) 这里是不是写错了,不是 obj[k],而是obj[key]。 |
|
ok,已改正,谢谢 @peakDragonCheung |
WeakMap 里有一段写成了 WeakSet。 |
Set 实例方法下面遍历方法中使用map、filter那个地方 打印写错了 应该是[2,3,4]和[4]吧 |
在 “与其他数据结构的相互转换 的 4.Object 转 Map” 参数名称写错了, 应该是 “obj” 而不是 "map" |
我觉得看这篇技术文章也可以 |
new Set([1,2,3]).length 值应该是undefind 而不是0 |
为了学习 ES6 里面 Set、Map、WeakSet 和 WeakMap 的知识,懒癌晚期的我第一选择立刻翻阅阮一峰老师的 ES6入门 ,挑选里面的局部内容进行摘抄和理解。 Set
看到这句我们可以基本明白,Set 是 “无重复的值的数组 (Array) ”。
然后我们会发现,Set 居然没有 push 、shift 这类的方法吗?不是说是无重复值的数组吗?
*可迭代对象 这里提到了可迭代对象,很多时候我们只记得可迭代对象一般是能够被 个屁!并不能。凭啥?我们来看看在 MDN 中的真正的定义:
内置字符串对象,数组,类数组……这些才是在 JS 语言中真正的可迭代对象(说起来字符串本身也是一种类数组哦)。所以刚刚的测试报错,我们就用 DevTools 所理解的伪数组(有 length 属性、且有 splice 方法的对象)来骚操作一下 : 为啥有问题?好好看报错! var foo = {
0 : 'zero',
1 : 'one',
2 : 'two',
3 : 'three',
length : 4
};
foo[Symbol.iterator] = function(){
let i = 0;
let l = this.length;
let that = this;
console.log('someone is using the iterator')
return {
next() {
if (i < l) {
console.log('now:'+that[i]+',progress:'+i+'/'+l)
return { done: false, value: that[i++] };
}
return { done: true };
}
};
}
new Set(foo); 关于迭代器的一些知识搜索来源于 David Tang 博客中的 《Iterables and Iterators in JavaScript》 ,原文干货很多,建议 Mark 。 就此打住,我们把重心转移回 Set 上。
理解到这里,用 Set 给一些存了基本类型数据的数组去重,就很好理解了。 Map阮一峰老师在文中有一个特别好的总结,我们摘录下:
const data = {};
const element = document.getElementById('myDiv');
data[element] = 'metadata';
data['[object HTMLDivElement]'] // "metadata"
粗暴理解下,Map 是一个可以用 “任何值” 作为 **键名 **的 对象 。更严谨地说,不是“任何值”,而是“任何指针”。可以用阮一峰老师的例子说明:
const map = new Map();
const k1 = ['a'];
const k2 = ['a'];
map
.set(k1, 111)
.set(k2, 222);
map.get(k1) // 111
map.get(k2) // 222
最好玩的是,Map 和 Set 的构造器所传参数是一样的——无参数、或者可迭代对象。 WeakSet顾名思义,WeakSet 是“弱 Set”——弱引用版本的 Set。光是知道这句话是不行的,很多同学在刚接触这个定义的时候会有这种猜想: // 以下代码的输出结果为猜想值
var ws = new WeakSet();
var a = {foo:'bar'};
ws.add(a);
console.log(ws);
/* 应输出:
WeakSet {{…}}
__proto__: WeakSet
[[Entries]]: Array(1)
0: value: {foo: "bar"}
length: 1
*/
delete a;
console.log(ws);
/* 应输出:
WeakSet {}
__proto__: WeakSet
[[Entries]]: Array(0)
length: 0
*/ 然后果不其然,我们会被 pia pia 打脸: 既然已经走到了这里,我们就一口气把 JavaScript 浏览器端和 WeakSet 相关的内存管理、弱引用等知识都搞清楚。首先我们了解下 JavaScript 里有关变量回收的一些规则(参考文章):
所以我们再修改一下上方的代码。 var test = {
name : 'test',
content : {
name : 'content',
will : 'be clean'
}
};
var ws = new WeakSet();
ws.add(test.content);
console.log('清理前',ws);
delete test.content;
console.log('清理后',ws) 原来,JavaScript 语言中,内存的回收并不是在执行 delete 操作符断开引用后即时触发的,而是根据运行环境的不同、在不同的运行环境下根据不同浏览器的回收机制而异的。比如在 Chrome 中,我们可以在控制台里点击 CollectGarbage 按钮来进行内存回收: 关于在不同浏览器环境下手动进行内存回收的具体异同,可参考:如何手动触发 JavaScript 垃圾回收行为? var test = {
name : 'test',
content : {
name : 'content',
will : 'be clean'
}
};
var ws = new WeakSet();
ws.add(test.content);
console.log('清理前',ws); // 清理前 WeakSet {{…}}
test.content = null;
console.log('清理后',ws); // 清理后 WeakSet {{…}}
// -- 进行手动回收 --
console.log(ws); // WeakSet {} 这样我们就彻底搞清楚了:JavaScript 会在执行内存回收时,清除掉 被引用次数为0 的那部分内存;而 WeakSet 是只能储存对象的(或者说只能储存内存指针而非静态值)、并且它对对象的引用将不计入对象的引用次数,当清除对象属性、对应的内存被清理之后,WeakSet 中记录的内存地址上不再有内容,它将自动断开与这条引用的关联 —— 也正因如此,它所储存的内容会受到开发者对其他对象操作的被动影响,所以 WeakSet 在设计上就设计成了没有“长度”、“遍历”概念的特殊弱引用 Set 型。 这样的弱引用,用途上可以开一些脑洞,比如阮一峰老师的例子: const foos = new WeakSet()
class Foo {
constructor() {
foos.add(this)
}
method () {
if (!foos.has(this)) {
throw new TypeError('Foo.prototype.method 只能在Foo的实例上调用!');
}
}
}
相比 WeakMap,它的应用能力不是特别强,或许这也是它目前没有被广泛支持的原因吧。 WeakMap理解了迭代器、弱引用、内存回收,对 WeakMap 我们就可以很简单地去理解了: var a = {b:{c:'42'}};
var wm = new WeakMap();
wm.set(a.b,'love & peace');
// WeakMap {{…} => "love & peace"}
delete a.b;
// 手动执行 CollectGarbage
console.log(wm);
// WeakMap {} 懂得很多道理,却依然过不好这一……呸!既然知道定义了就应该知道怎么用!我们先以阮一峰老师的例子 A 来看: 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);
把 DOM 节点用作它的键名是一个常见场景,对应的可以做各种各样的骚操作。再看阮一峰老师的例子 B : 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
在这两个例子的基础上,我的理解是:WeakMap 非常擅长去配合 非常态的实例、节点、属性 一同使用,在那些内容被销毁时跟着一起被回收。很多时候我们不得不用一些变量来给这些东西做各种各样的辅助,比如 计数器、状态标识、临时值储存……在这种情况下,我们学习了 WeakMap ,就可以用 WeakMap 来做这个辅助的集中管理。 顺带一提, WeakMap 的浏览器支持性完爆 WeakSet …… 感悟虽然最初只是想大概知道下这几个 ES6 新出的小老弟是干啥用的,不过顺便就把所有的相关知识都梳理了下。我们已经可以看到这些 ES6 的福利正在逐渐普及,和我一样是万年切图仔的同学们也要适当充实下自己在基础方面的知识,不要只知其然不知其所以然啦~ |
@sisterAn 差集有问题啊 |
Map: key可为任何类型的键值对 |
WeakSet和Set都是构造函数,可以使用new命令创建相应的数据结构,并且值都是唯一的; Map和WeakMap都是构造函数,用域生成键值对的集合; |
|
Set与WeakSet区别:
Map与WeakMap区别:
弱引用最大的特点就是: 我们有时候需要对对象添加一些数据, 但是又不希望把该引用计入到引用计数影响了GC。 |
1.Set其实就是类数组对象 , 不是标准的数组,没有下标所以不能使用for 循环,但是能使用forEach for of 循环
也可以结合Array from 方法实现数组去重
|
总结:
|
const s = new Set([1,3,3,4,4,2])
// {1,3,4,2} 内部成员唯一性
const m = new Map([
["name","Kisn"],
["age","25"]
]) 内部成员-键存在唯一性
WeakMap和WeakSet 都不会 |
Map 的key 可以为引用类型,也可以为基础数据类型,可以理解为对javascript普通对象的扩展。js普通对象key只能为字符串。 Set 为非重复元素的集合,Set 集合里面的每一项不能重复,引用类型则是地址不能重复,基础类型则是值不能重复。 |
应该不能说 Set 可以使用 map filter 方法吧,只是 Set 和数组互转很方便,结合数据的 map filter 方法,可以实现很方便的交集、并集、差集。 |
关于weakMap与weakSet弱引用该如何理解
弱引用是向weakSet/weakMap中添加一个目标对象的引用,但添加是目标对象的引用计数不增加。比较来说:
到这里,由于对象的引用计数为0了,所以weakSet中的那个被add()进去的x、y就自动被回收了。——weakSet/weakMap具备这种机制。 所以weakSet/weakMap没有size这个属性,它不安全。——你刚读了它的值,它自己自动回收了一下,就又变掉了。 如果感兴趣大家可以通过链接去订阅(只想表示很硬核)http://gk.link/a/10fj0 |
@sisterAn 差集错了,应该是 let difference = [...new Set([...set1, ...set2])].filter(value => !new Set([...set1].filter(value => set2.has(value))).has(value)) |
老哥你很喜欢健身吗? |
Set不是没有键名,是键名和值相同,const set = new Set() set.add(444) set.keys()可获得键名的Iterator对象,set.forEach可遍历 |
Set: 1. 成员唯一、无序且不重复 2. [value, value],键值与键名是一致的(或者说只有键值,没有键名) |
🧡 Map类似于对象,它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
🧡 Set类似于数组,与数组的区别是它的成员值都是唯一的,是不重复的。
🧡 WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。
🧡 WeakMap结构与Map结构类似,也是用于生成键值对的集合。WeakMap与Map的区别有两点:首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。 |
Set对象并非只有键值没有键名 只是键值键名一样而已。 |
整理的很不错,学习了~ |
WeakSet与Set不同点: WeakMap与Map不同点: |
set确定能使用map、filter方法吗?我自己试是报错的哇,数组的方式应该不能给set用吧 |
用call就行了,因为set是可迭代的。 |
map转obj那块,需要加上判断key的类型,因为map的key可以是任意类型,而obj不可以 |
http://es6.ruanyifeng.com/#docs/set-map
看完这个就可以了
The text was updated successfully, but these errors were encountered: