Set,Map,weakSet,weakMap

80 阅读11分钟

Set

基本用法:类似于数组,但是成员的值都是唯一的没有重复,Set本身是一个构造函数,用来生成Set数据结构

 const s=new Set();
    [2,3,4,5,1,2,2,2,2].forEach(x=>s.add(x));
    console.log(s.size);
    for(let i of s){
        console.log(i);
    }

Set函数可以接受一个数组(或者具有iterable接口的其他数据结构)作为参数,用来初始化

const set=new Set([1,2,3,3,3,]);

Set结构不会添加重复的的值,可以利用这个特性来实现数组去重【[...new Set(Array)]】

const s=new Set([1,1,1,22,3,4,5]);
console.log([...s]);

【向Set加入值时不会发生类型转换,所以5和“5”是两个不同的值,Set内部判断两个值是否相同时使用的算法是“Same-value equality”,它类似于精确相等运算符(===),主要区别是NAN等于自身,但是精确运算符认为NAN不等于自身】

const set=new Set();
    let a=NaN;
    let b=NaN;
    set.add(a);
    set.add(b);
    console.log(set);//NaN

向代码中添加了两个NAN但是实际上只有一个,表明在Set内部两个NaN相等的,但是两个空对象在Set内部是不相等的【两个空对象不是精确相等

const set =new Set();
    set.add({});
    set.add({});
    console.log(set);//{{},{}}

Set实例的属性和方法

Set实例的属性:

  • Set.prototype.constructor:构造函数,默认就是Set函数
  • Set.prototype.size:返回Set实例的成员总数

Set实例的方法:(操作方法和遍历方法)

操作方法

  • add(value):添加某个值,返回Set结构本身
  • delete(value):删除某个值,返回一个布尔值,表示删除是否成功
  • has(value):返回一个布尔值,表示参数是否为Set的成员
  • clear():清除所有的成员,没有返回值
const properties=new Set();
properties.add('width');
properties.add('height');
if(properties.has('width')){
    
}

Array.from方法可以将Set结构转换为数组

const items=new Set([1,2,3,4,5,1]);
const array=Array.from(items);
console.log(array);

遍历方法

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员

Set的遍历顺序就是插入顺序,使用Set保存一个回调函数列表,调用时就能保证按照添加顺序调用】

keys,values,entries返回的都是遍历器对象,由于Set结构没有键名,只有键值(或者说键值和键名是同一个值)。所以keys和values方法的行为是一致的

 	let set=new Set(['r','g','b']);
    for(let item of set.keys()){
        console.log(item);
    }
    for(let item of set.values()){
        console.log(item);
    }
    for(let item of set.entries()){
        console.log(item);
    }

【Set结构的实例默认可以遍历,其默认遍历器生成函数就是它的values方法,可以省略values方法,直接用for...of循环遍历Set】

let set=new Set(['r','g','b']);
for(let x of set){
    console.log(x);
}

Set结构实例forEach方法用于对每个成员执行操作, 没有返回值

let set=new Set([1,2,3]);
set.forEach((key,value)=>console.log(value*2));
遍历的应用
  • 扩展运算符(...)内部使用for...of循环,所以也可以用于Set结构

    	let set=new Set(['r','g','b']);
        let arr=[...set];
        console.log(arr);
    

    扩展运算符和Set结构相结合就可以去除数组重复的成员

  • 数组的map和filter方法也可以用于Set

     	let set=new Set([1,2,3]);
        set =new Set([...set].map((item)=>item*2));
        console.log(set);
        set=new Set([...set].filter(x=>x%3===0));
        console.log(set);
    
  • 使用Set可以很容易地实现并集(Union),交集(Intersect)和差集(Difference)

    	let a=new Set([1,2,3]);
        let b=new Set([2,3,4]);
        // 并集
        let union=new Set([...a,...b]);
        console.log(union);
        // 交集
        let Intersect=new Set([...a].filter(x=>b.has(x)));
        console.log(Intersect);
        // 差集
        let Difference=new Set([...a].filter(x=>!b.has(x)));
        console.log(Difference);
    
  • 没有直接的方法在遍历操作中同步改变Set结构

    //方法一
    let set=new Set([1,2,3]);
    set=new Set([...set].map(x=>x*2));
    //方法二
    let set=new Set([1,2,3]);
    set=new Set(Array.from(set,val=>val*2));
    

Map

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

基本用法

    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
    console.log(m.delete(o));
    console.log(m.has(o));//false

map也可以接受一个数组作为参数,该数组的成员是一个个表示键值对的数组

const map=new Map([
        ['name','122'],
        ['title','111']
    ])
    map.set('123','111111');
    console.log(map);
    console.log(map.size);
    console.log(map.get('name'));
    console.log(map.has('title'));

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

	const set=new Set([
        ['foo',1],
        ['bar',2]
    ])
    console.log(set);
    console.log(set.has('foo'));
    const m1=new Map(set);
    console.log(m1);
    const m2=new Map(m1);
    console.log(m2);

如果用一个键多次赋值,后面的值会覆盖前面的值

const map=new Map();
map.set(1,2).set(1,1);
console.log(map.get(1));//1

【如果读取一个未知的键,则返回undefined,只有对同一个对象的引用,Map结构才将其视为同一个键】

    const map=new Map();
    map.set(['a'],111);
    console.log(map.get(['a']));//undefined

set和get方法表面是针对同一个键,实际上却是两个值,内存地址不一样,因此get方法无法读取该键,返回undefined

    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的键实际上是和内存地址绑定的,只要内存地址不一样,就视为两个键,这就解决了同名属性碰撞的问题

【如果Map的键是一个简单类型的值(数字,字符串,布尔值),则只要两个值严格相等,Map就将其视为一个键,包括0和-0,虽然NaN不严格等于自身,但是Map将其视为同一个键

    let map=new Map();
    map.set(-0,123);
    console.log(map.get(+0));//123
    map.set(true,1);
    map.set('true',2);
    console.log(map.get(true));//1
    map.set(undefined,3);
    map.set(null,4);
    console.log(map.get(undefined))//3
    map.set(NaN,123);
    map.set(NaN,1234);
    console.log(map.get(NaN));//1234
    // console.log(map);

实例的属性和操作方法

  • size属性:返回Map结构的成员总数
  • set(key,value):设置key对应的键值,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就生成新的键【可以采用链式写法】
  • get(key):读取key对应的键值,如果找不到则返回undefined
  • has(key)返回一个布尔值,表示某个键是否在Map中
  • delete(key):删除某个键,返回true,失败返回false
  • clear():清除所有成员,没有返回值

遍历方法

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回所有成员的遍历器
  • forEach();遍历Map的所有成员

【Map的遍历顺序就是插入顺序】

    // 遍历Map
    const map=new Map([
        [1,2],
        [3,4]
    ]);
    for(let key of map.keys()){
        console.log(key);
    }
    for(let [key ,value] of map.entries()){
        console.log(key,value);
    }
    for(let item of map){
        console.log(item[0],item[1]);
    }
//Map结构的默认遍历器接口(Symbol.iterator属性)就是entries方法
console.log(map[Symbol.iterator]===map.entries)//true
const map=new Map([
    [1,2],[3,4],[5,6]
]);
map.forEach(function (value,key,map){
    console.log("key:%s,value:%s",key,value);
})
//forEach还可以接受第二个参数this
//map.forEach(function (value,key,map){
    //console.log("key:%s,value:%s",key,value);
//},repoter);

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

  • Map转换为数组:使用(...)扩展运算符

    const myMap=new Map();
    myMap.set(true,1).set(1,2);
    console.log([...myMap]);
    
  • 数组转换为Map:将数组传入Map构造函数中就可以

  • Map转换为对象

     // Map转换为对象
        function ToObj(map){
            let obj=Object.create(null);
            for(let [k,v] of map){
                obj[k]=v;
            }
            return obj;
        }
        const map=new Map([
            ['name',12],
            ['age',134]
        ])
        let obj=new Object();
        obj=ToObj(map);
        console.log(obj);
    
  • 对象转换为Map

    function objToMap(obj){
            let map=new Map();
            for(let k of Object.keys(obj)){
                map.set(k,obj[k]);
            }
            return map;
        }
        const obj={
            name:'jack',
            str:11
        }
        let map=objToMap(obj);
        console.log(map);
    

weakMap

weakMap是一种弱映射的新的集合类型,有增强了的键/值对的存储机制,weakMap是map的兄弟类型,其API也是Map的字节,weakMap中的weak描述的是js垃圾回收程序对待弱映射中键的方式

基本用法

const wm=new weakMap()

弱映射中的键只能是Object或者继承自Object的类型,尝试使用非对象设置键会抛出typeError,值的类型没有限制

  • 如果想要在初始化时填充弱映射,则构造函数可以接收一个可迭代对象,其中需要包含键/值对数组,可迭代对象中的每个键/值都会按照迭代顺序插入新实例中

    const key1={id:1},key2={id:2},key3={id:3};
    const wm=new WeakMap([
        [key1,1],
        [key2,2],
        [key3,3]
    ])
    console.log(wm);
    
  • 初始化是一个全有或全无的操作,只要有一个键无效就会抛出错误,导致整个初始化失败

  • 原始值可以包装成对象再用作键

    const stringKey=new String('key1');
    const wm2=new WeakMap([
        [stringKey,1]
    ])
    console.log(wm2.get(stringKey));
    
  • 初始化之后可以使用set()再添加键/值对,可以使用get()和has()查询,还可以使用delete()删除

  • set()方法返回弱映射实例,因此可以把多个操作连缀起来,链式调用

弱键

这些键不属于正式引用,不会阻止垃圾回收,只要键存在,键/值对就会存在于映射中,并被当作对值得引用,因此不会被当作垃圾回收

const wm3=new WeakMap();
wm3.set({},'1');
//set方法初始化了一个新对象并将它用作一个字符串得键,因为没有指向这个对象得其他引用,所以当这行代码执行完成之后这个对象键就会被当作垃圾回收,然后这个键/值对就从弱映射中消失,使其成为一个空映射,所以再这个例子中,因为值没有被引用,所以这对键值对被破坏后,值本身也会成为垃圾回收得目标

const wm4=new WeakMap();
const container={
    key:{};
}
wm4.set(container.key,1);
//container对象维护着一个对弱映射键得引用,因此这个对象不会成为垃圾回收得目标,如果将container.key=null,则会摧毁对象得最后一个引用,垃圾回收程序就可以把这个键值对清理掉

不可迭代键

  • 因为weakMap中得键值对在任何时候都有可能被销毁,所以没必要提供迭代其键值对的能力,weakMap也没有clear这种一次性销毁所有键/对的方法

  • 因为不可迭代,所以也不可能在不知道对象引用的情况下从弱映射中取得值,即便代码可以访问weakMap实例,也没办法看到其中的内容

  • weakMap实例之所以限制只能使用对象作为键,是为了保证只有通过键对象的引用才能取得值,如果允许原始值,那就没办法区分初始化时使用的字符串字面量和初始化之后使用的一个相等的字符串了

    【因为基础类型是按值传递的,给一个Map实例设置一个基础类型的变量为键,就意味着是把这个变量的值复制了一份给Map实例,实例中的键与原来的这个变量就失去了联系,即便原来的这个变量的内存被回收了,实例中的键也存在,重新创建一个同值得变量就可以获得键值对得信息,而引用来行是按引用传递的,实例中得键其实是对变量内存地址得弱引用,变量一旦被回收,WeakMap中得引用也被销毁了】

使用弱映射

  • 私有变量
  • 给DOM节点添加元数据

WeakSet

WeakSet是set的兄弟类型,其API也是Set的字节,WeakSet中的weak描述的是js垃圾回收程序对待弱集合中的值的方式

基本用法

const ws=new WeakSet()

  • 弱集合中的值只能是Object或者继承自Object的类型,尝试使用非对象设置值会抛出TypeError

    const val1={id:1};
    const val2={id:2};
    const ws1=new WeakSet([val1,val2]);
    console.log(ws1.has(val1));//trur
    
  • 初始化是全有或全无的操作,只要有一个值无效就会抛出错误,导致整个初始化失败

    const ws2=new WeakSet([val1,'BADVAL',val2]);
    //typeError
    
  • 原始值可以先包装成对象在作用值

    const stringKey=new String('val1');
    const ws3=new WeakSet([stringKey]);
    console.log(ws3.has(stringKey));
    
  • 初始化之后可以使用add()再添加新值,可以使用has()查询,还可以使用delete()删除

  • add()方法返回弱集合实例,因此可以把多个操作连缀起来,包括初始化声明(链式调用)

弱值

WeakSet表示的式弱集合的值,这些值不属于正式的引用,不会组织垃圾回收

const ws=new WeakSet();
ws.add({});

//add()方法初始化了一个新对象,并将它用作一个值,因为没有指向这个对象的其他引用,所以当这行代码执行完成之后,这个对象就会被当作垃圾回收,然后这个值就从弱集合中消失了,使其成为了一个空集合

不可迭代值

因为WeakSet中的值任何时候都有可能会被销毁,所以没必要提供迭代其值的能力,也没有clear()这样一次性销毁所有值的方法因为不可以迭代,所以也不可能直到对象引用的情况下从弱集合中取得值,即便代码访问WeakSet实例,也没办法看到其中的内容

使用弱集合

  • 给DOM节点添加元数据