目录
Map
含义与用法
实例的属性和操作方法
遍历方法
WeakMap
含义
WeakMap的语法
WeakMap的用途
JavaScript的对象,本质上是键值对的集合(hash结构),但是传统上只能用字符串作为建,这有了很大的限制。
const data = {};
const element = document.getElementsByTagName("div")[0];
data[element] = "metadata";
console.log(data['[object HTMLDivElement]']); //metadata
//上面代码原意是将一个Dom节点作为对象data的键,但是由于对象只接受字符串作为键名,所以element被自动的转为了字符串。
Es6提供了Map数据结构,它类似于对象,也是键值对的集合,但是‘键’的范围不限于字符串,各种类型的值(包括对象)都可以作为键名。也就是说,Object结构提供了“字符串-值”的对应,Map结构提供了“值-值”的对应。是一种更完善的Hash结构实现。如果你需要‘键值对’的数据结构,Map比Object更适合。
const m = new Map();
const o = {
p : "Hello World",
}
m.set(o,"content");
console.log(m.get(o)); //content
console.log(m.has(o)); //true
m.delete(o);
console.log(m.has(o)); //false
Map作为构造函数,也是可以接受一个数组作为参数。该数组的成员表示键值对的数组。
const map = new Map([
["name","张三"],
["title","Author"],
])
console.log(map.get('name')); //张三
console.log(map.get("title")); //Author
console.log(map);
Map构造函数接受数组作为参数,实际上执行的是以下算法。
const items = [
["name","张三"],
["title","Author"],
]
const map = new Map();
items.forEach(([key,value]) => map.set(key,value));
console.log(map);
事实上,不仅是数组,任何具有Iterator接口,且每个成员都是一个双元素的数组的数据结构都可以当做Map构造函数的参数。这就是说,Set和Map都可以用来生成新的Map。
const set = new Set([
["foo",1],
]);
const m1 = new Map(set);
console.log(m1);
const m2 = new Map([
["bar",2],
])
const m3 = new Map(m2);
console.log(m3);
在Map结构中,如果对同一个值复制多次,后面的值将覆盖前面的值。
const map = new Map();
map
.set(1,"aaa")
.set(1,"bbb");
console.log(map.get(1)); //bbb aaa被覆盖
如果读取一个未知的键,则返回undefined。
console.log(new Map().get("wang")); //undefined
注意:只有对同一个对象的引用,Map结构才将其视为同一个键。引用值由于存储地址不同,相同的会被视为两个值。
const map = new Map();
map.set([123],"123");
console.log(map.get([123])); //undefined
同理,同样的值的两个实例,在Map结构中被视为两个键。
const map = new Map();
const k1 = ['a'];
const k2 = ['a'];
map.set(k1,111);
map.set(k2,222);
console.log(map.get(k1)); //111
console.log(map.get(k2)); //222
如果Map的键是一个原始数据类型,只要两个键严格相同,就视为是同一个键。
const map = new Map();
map.set(-0,1);
console.log(map.get(+0)); //1
map.set(true,2);
map.set('true',3);
console.log(map.get(true)); //2
map.set(undefined,4);
map.set(null,5);
console.log(map.get(undefined)); //4
map.set(NaN,6);
console.log(map.get(NaN)); //6
size属性
size属性返回Map结构的成员总数。
const map = new Map();
map.set("foo",1).set('bar',2);
console.log(map.size); //2
set(key,value)
set方法设置键名key对应的键值value,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就新生成该键。
const m = new Map();
m.set("edition",6);
m.set(123,"standard");
m.set(undefined,'nah');
set方法返回的是当前的Map对象,因此可以采用链式写法。
const map = new Map();
map.set("foo",1).set("bar",2).set("baz",3);
console.log(map);
get(key)
get方法读取key对应的键值,如果找不到key,返回undefined。
const map = new Map();
const hello = () => {
console.log("hello");
}
map.set(hello,"Hello ES6");
console.log(map.get(hello)); //Hello ES6
has(key)
has方法返回一个布尔值,表示某个键是否在当前Map对象中。
const map = new Map();
map.set("foo",1);
console.log(map.has("foo")); //true
console.log(map.has("bar")); //false
delete(key)
delete方法删除某个键,返回true,如果删除失败,返回false。
const map = new Map();
map.set("foo",1);
console.log(map.delete("foo")); //true
console.log(map.delete("foo")); //false
clear()
clear方法清除所有成员,没有返回值。
const map = new Map();
map.set("foo",1).set("baz",2);
console.log(map.size); //2
map.clear();
console.log(map.size); //0
Map结构原生提供是三个遍历器生成函数和一个遍历方法。
keys():返回键名的遍历器。
values():返回键值的遍历器。
entries():返回所有成员的遍历器。
forEach():遍历Map的所有成员。
注意:Map的遍历顺序是插入顺序。
const map = new Map([
["foo",1],
["bar",2],
])
for(let key of map.keys()){
console.log(key);
}
for(let value of map.values()){
console.log(value);
}
for(let items of map.entries()){
console.log(items);
}
Map结构的默认遍历器接口就是entries方法。
const map = new Map();
console.log(map[Symbol.iterator] === map.entries); //true
Map结构转为数组可以用扩展运算符。
const map = new Map([
[1,"foo"],
[2,"bar"],
[3,"baz"]
])
console.log([...map.keys()]) //[1,2,3]
console.log([...map.values()]); //['foo','bar','baz']
console.log([...map.entries()]); //[[1,'foo'],[2,'bar'],[3,'baz']]
结合数组的map方法,filter方法,可以实现Map的遍历和过滤。
const map = new Map([
[1,'a'],
[2,'b'],
[3,'c'],
]);
const map1 = new Map([...map].filter(([k,v])=> k < 3));
console.log(map1);
const map2 = new Map([...map].map( ([k,v]) =>[ k * 2,v]));
console.log(map2);
Map有一个forEach方法,与数组的forEach方法类似,也可以实现遍历,还可以接受第二个参数,用来绑定this。
const map = new Map();
map.set(1,"for").set(2,"bar").set(3,"baz");
map.forEach( (value,key,map) => {
console.log(key,value);
})
const reporter = {
report(key,value) {
console.log("Key %s","Value %s",key,value);
}
}
map.forEach( function (value,key,map) {
this.report(key,value);
},reporter);
Weakmap结构与Map结构类似,也是用于生成键值的集合。
WeakMap可以使用Set方法添加成员。
const wm1 = new WeakMap();
const key = {foo : 1};
wm1.set(key,2);
console.log(wm1.get(key)); //2
WeakMap 也可以接受一个数组,作为构造函数的参数。
const k1 = [1,2,3];
const k2 = [4,5,6];
const wm = new WeakMap([[k1,"foo"],[k2,"bar"]]);
console.log(wm.get(k2)); //bar
WeakMap与Map有两点区别。
第一:和WeakSet一样,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
第二:WeakSet的键名所指的对象,不计入垃圾回收机制。
WeakMap的设计目的在于,有时我们想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。一旦不再需要这两个对象,我们就必须手动删除这个引用,否则垃圾回收机制就不会释放对象占用的内存。
const e1 = document.getElementById("foo");
const e2 = document.getElementById("bar");
const arr = [
[e1,"foo元素"],
[e2,"bar元素"],
]
//不再使用必须手动删除引用
arr[0] = null;
arr[1] = null;
WeakMap 就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
基本上,如果你要往对象上添加数据,又不想干扰垃圾回收机制,就可以使用 WeakMap。一个典型应用场景是,在网页的 DOM 元素上添加数据,就可以使用WeakMap结构。当该 DOM 元素被清除,其所对应的WeakMap记录就会自动被移除。
const wm = new WeakMap();
const element = document.getElementsByTagName("div")[0];
wm.set(element,"some information");
console.log(wm.get(element));
注意的是,WeakMap弱引用的只是键名,而不是键值。键值依然是正常引用。
const wm = new WeakMap();
let key = {};
let obj = {foo : 1};
wm.set(key,obj);
obj = null;
console.log(wm.get(key)); //{foo : 1}
WeakMap和WeakSet相似,没有遍历方法和清空方法,WeakMap只有四个方法可以使用:get(),set(),has(),delete()。
const wm = new WeakMap();
let key = {};
wm.set(key,"123");
console.log(wm.get(key)); //123
wm.delete(key);
console.log(wm.get(key)); //undefined
console.log(wm.size) //undefined
console.log(wm.forEach) //undefined
console.log(wm.clear) //undefined
WeakMap应用的典型场合就是Dom节点作为键名。
let myElement = document.getElementsByTagName('div')[0];
let myWeakMap = new WeakMap();
myWeakMap.set(myElement,{timesClicked: 0});
myElement.addEventListener('click',function(){
let logoData = myWeakMap.get(myElement);
console.log(logoData.timesClicked ++);
},false)
//上面代码中,myElement是一个 DOM 节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,
//对应的键名就是myElement。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。
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('DOWM'));
c.dec();
c.dec(); //DOWN
上面代码中,Countdown类的两个内部属性_counter和_action,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏。
主页传送门