总结
- 1、
Map
和Set
中对象的引用都是强类型化的,并不会允许垃圾回收
。 - 2、
Map
和Set
的遍历顺序就是插入顺序 - 3、
WeakMap
和WeakSet
都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用
- 4、
WeakMap
和WeakSet
都是不可遍历
的、都没有size
属性、都没有clear
方法
Set:类似于数组,但是成员的值都是唯一的,没有重复的值
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4
Set函数可以接受一个数组
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
console.log(set);//Set { 1, 2, 3, 4 }
// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
// 例三
const set = new Set(document.querySelectorAll('div'));
set.size // 56
// 类似于
const set = new Set();
document
.querySelectorAll('div')
.forEach(div => set.add(div));
set.size // 56
利用Set对数组、字符串去重
数组去重
//1
let array=[1,2,3,4,5,5,3,2,1]
array=[...new Set(array)]
console.log(array);//[ 1, 2, 3, 4, 5 ]
//2
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]
字符串去重
var str=[...new Set('ababbc')].join('')
console.log(str);// "abc"
在 Set 内部,两个NaN是相等
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set // Set {NaN}
在 Set 内部,两个对象总是不相等的
let set = new Set();
set.add({});
set.size // 1
set.add({});
set.size // 2
只有两个对象的地址相同 才相等
let a={};
let b=a;
let set=new Set();
set.add(a);
set.add(b);
console.log(set); //Set { {} }
Set 实例的属性和方法
Set 结构的实例有以下属性
Set.prototype.constructor
构造函数,默认就是Set函数
。
Set.prototype.size
返回Set实例的成员总数
。
四个操作方法
add(value)
添加某个值,返回 Set 结构本身
。
delete(value)
删除某个值,返回一个布尔值
,表示删除是否成功
。
has(value)
返回一个布尔值
,表示该值是否为Set的成员
。
clear()
清除所有成员,没有返回值
。
var s=new Set();
s.add(1).add(2).add(2);
// 注意2被加入了两次
s.size // 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
s.delete(2);
s.has(2) // false
s.clear();
console.log(s); //Set {} size为0
Object结构和Set结构判断是否包括一个键
// 对象的写法
const properties = {
'width': 1,
'height': 1
};
if (properties[someName]) {
// do something
}
// Set的写法
const properties = new Set();
properties.add('width');
properties.add('height');
if (properties.has(someName)) {
// do something
}
Array.from方法可以将 Set 结构转为数组
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
console.log(array);//[ 1, 2, 3, 4, 5 ]
四个遍历方法
keys()
返回键名
的遍历器
values()
返回键值
的遍历器
entries()
返回键值对
的遍历器,同时包括键名和键值,每次输出一个数组
这个数组包含两个成员两个成员完全相等
forEach()
使用回调函数
遍历每个成员
Set的遍历顺序就是插入顺序
keys(),values(),entries()
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
forEach()
let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9
扩展运算符(...)内部使用for...of循环
所以也可以用于 Set 结构。
let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['red', 'green', 'blue']
数组的map和filter方法也可以间接用于 Set
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}
let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}
并集(Union)、交集(Intersect)和差集(Difference)
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}
WeakSet,结构与 Set 类似,也是不重复的值的集合
WeakSet与 Set 有两个区别
首先,WeakSet 的成员只能是对象,而不能是其他类型的值
其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用
const ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set
WeakSet 适合临时存放一组对象
,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet
里面的引用就会自动消失。
WeakSet、WeakMap 不可遍历
WeakSet 的成员是不适合引用
的,因为它会随时消失
。另外,由于 WeakSet 内部有多少个成员
,取决于垃圾回收机制有没有运行
,运行前后很可能成员个数是不一样的,而垃圾回收机制何时运行
是不可预测
的,因此 ES6 规定 WeakSet 不可遍历
。
这些特点同样适用于本章后面要介绍的 WeakMap
结构。
WeakSet没有size属性,没有办法遍历它的成员
语法,const ws = new WeakSet();
WeakSet 是一个构造函数,使用new命令,创建 WeakSet 数据结构
const ws = new WeakSet();
参数,可以接受一个数组或类似数组的对象作为参数
WeakSet 可以接受一个数组或类似数组
的对象
作为参数。(实际上,任何具有 Iterable 接口的对象
,都可以作为 WeakSet 的参数。)该数组的所有成员,都会自动成为 WeakSet
实例对象的成员。
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}
是a
数组的成员
成为 WeakSet
的成员,而不是a数组本身
const b = [3, 4];
const ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(…)
数组b的成员不是对象
,加入 WeaKSet 就会报错
。
三个方法
WeakSet.prototype.add(value)
向 WeakSet 实例添加
一个新成员。
WeakSet.prototype.delete(value)
清除
WeakSet 实例的指定成员。
WeakSet.prototype.has(value)
返回一个布尔值,表示某个值是否存在
WeakSet 实例之中。
const ws = new WeakSet();
const obj = {};
const foo = {};
ws.add(window);
ws.add(obj);
ws.has(window); // true
ws.has(foo); // false
ws.delete(window);
ws.has(window); // false
用途
WeakSet 的一个用处,是储存 DOM 节点
,而不用担心这些节点从文档移除时,会引发内存泄漏。
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,类似于对象,但“键”不限于字符串,各种类型的值(包括对象)都可以当作键
Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应
//传统 对象
const data = {};
const element = document.getElementById('myDiv');
data[element] = 'metadata';
data['[object HTMLDivElement]'] // "metadata"
//element被自动转为字符串'[object HTMLDivElement]'
//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
参数
Map 接受一个数组作为参数,数组的成员是一个个表示键值对的数组 :[['name', '张三']]
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"
原理:
const items = [
['name', '张三'],
['title', 'Author']
];
const map = new Map();
items.forEach(
([key, value]) => map.set(key, value)
);
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
关于Map的键
1、对同一个键多次赋值,后面的值将覆盖前面的值(与对象相同)
const map = new Map();
map
.set(1, 'aaa')
.set(1, 'bbb');
map.get(1) // "bbb"
2、读取一个未知的键,默认undefined(与对象相同)
new Map().get('asfddfsasadf')
// undefined
3、只有对同一个对象的引用,Map 结构才将其视为同一个键 - 重点注意
//引用地址(内存地址)不相同
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
//引用地址(内存地址)相同
var a=['a'];
var b=a;
const map = new Map();
map.set(a, 555);
console.log(map.get(b)); // 555
4、当Map 的键是简单类型的值时(数字、字符串、布尔值)只要两个值严格相等(===),Map 将其视为一个键
比如0和-0就是一个键
布尔值true和字符串true则是两个不同的键
undefined和null也是两个不同的键
虽然NaN不严格相等于自身,但 Map 将其视为同一个键
let map = new Map();
map.set(-0, 123);
map.get(+0) // 123
map.set(true, 1);
map.set('true', 2);
map.get(true) // 1
map.set(undefined, 3);
map.set(null, 4);
map.get(undefined) // 3
map.set(NaN, 123);
map.get(NaN) // 123
属性和操作方法
size 属性
const map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
set(key, value)
const m = new Map();
m.set('edition', 6) // 键是字符串
m.set(262, 'standard') // 键是数值
m.set(undefined, 'nah') // 键是 undefined
let map = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
console.log(map); //Map { 1 => 'a', 2 => 'b', 3 => 'c' }
get(key)
const m = new Map();
const hello = function() {console.log('hello');};
m.set(hello, 'Hello ES6!') // 键是函数
m.get(hello) // Hello ES6!
has(key)
const m = new Map();
m.set('edition', 6);
m.set(262, 'standard');
m.set(undefined, 'nah');
m.has('edition') // true
m.has('years') // false
m.has(262) // true
m.has(undefined) // true
delete(key)
const m = new Map();
m.set(undefined, 'nah');
m.has(undefined) // true
m.delete(undefined)
m.has(undefined) // false
clear()
let map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
map.clear()
map.size // 0
Map的遍历
keys():
返回键名
的遍历器。
values():
返回键值
的遍历器。
entries():
返回所有成员(键值对)
的遍历器。
forEach():
使用回调函数
遍历 Map 的所有成员。
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for (let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
Map 的forEach方法
数组的forEach方法类似,也可以实现遍历
map.forEach(function(value, key, map) {
console.log("Key: %s, Value: %s", key, value);
});
forEach方法还可以接受第二个参数,用来绑定this
const reporter = {
report: function(key, value) {
console.log("Key: %s, Value: %s", key, value);
}
};
map.forEach(function(value, key, map) {
this.report(key, value);
}, reporter);
运用... 将Map 结构转为数组结构
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['one', 'two', 'three']
[...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map]
// [[1,'one'], [2, 'two'], [3, 'three']]
Map 的遍历和过滤
结合数组的map方法、filter方法实现,(Map 本身没有map和filter方法
)
const map0 = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
const map1 = new Map(
[...map0].filter(([k, v]) => k < 3)
);
// 产生 Map 结构 {1 => 'a', 2 => 'b'}
const map2 = new Map(
[...map0].map(([k, v]) => [k * 2, '_' + v])
);
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}
与其他数据结构的互相转换
Map 转为数组,使用扩展运算符(...)
const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap]
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
数组 转为 Map
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
Map 转为对象
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
对象转为 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})
// Map {"yes" => true, "no" => false}
Map 转为 JSON
Map 转为 JSON 要区分两种情况。
情况1,Map 的键名都是字符串,这时可以选择转为对象 JSON
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
情况2,Map 的键名有非字符串,这时可以选择转为数组 JSON
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}
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
WeakMap
WeakMap与Map的区别
1、WeakMap只接受对象作为键名(null除外)
const 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
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占用的内存。
要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap
//WeakMap存储
const wm = new WeakMap();
const element = document.getElementById('example'); //引用数 1
wm.set(element, 'some information'); //引用数还是1
wm.get(element) // "some information"
上面的 DOM 节点对象的引用计数是1,而不是2。这时,一旦消除对该节点的引用(element = null),它占用的内存就会被垃圾回收机制释放。
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 的语法
WeakMap 与 Map 在 API 上的区别主要是两个:
WeakMap只有四个方法可用:
get()
set()
has()
delete()
WeakMap 的用途
1、典型场合就是 DOM 节点作为键名
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 节点删除,该状态就会自动消失,不存在内存泄漏风险。
2、部署私有属性
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
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!