• 数据结构---Set和Map


    1.Set数据结构

    Set本质上是一个没有重复数据,但是具有irerator接口可以遍历的一种集合。

    Set本身也是一种数据结构的构造函数。

    1.Set的初始化

    var obj = new Set(参数);

    上面生成一个Set的实例,obj是集合对象,可以通过for...of遍历。

    参数可以是数组,也可以是类数组(具有iterator接口的数据,如字符串)

    var obj = [...new Set([1,3,3,3])]; // [1,3]
    var obj = [...new Set('hellohello')]; // ['h','e','l','o'].join('')--'helo'

    注意new Set()生成的对象是类数组,通过[...]转为数组。

    ⚠️:[...new Set(数组或者类数组)] 可以去重!!!

    // +0/-0是一个值 NaN等于自身
    [...new Set([+0,-0,NaN, NaN])];  // [0, NaN]

     两个对象总是不相等,该去重方法不适用于对象(任何形式的对象)!

    [...new Set([{}, {}, {}])]; // [{}, {}, {}]
    // 对象指的的是存储地址,所以每个对象都不一样,所以,不会被认为是重复数据

    对象去重参考

    ⚠️将Set结构转为数组还有一个方法Array.from(set对象);Array.from可以将所有的类数组(含length)转为数组

    let obj = Array.from(new Set([1,23,3,3]))

    扩展运算符(...)和Array.from() 方法本质也是调用for...of循环。

    2.Set的实例属性和方法

    1.实例属性

    1. Set.prototype.constructor

    实例对象的构造函数,继承自原型对象

    const obj = new Set('a');
    obj.constructor; //Set  Set.prototype.constructor === Set

    2. Set.prototype.size

    实例对象的元素去重后的个数;相当于数组的长度属性

    const obj = new Set([1,2,1,2,3]);
    obj.size; // 3
    const obj = new Set('aabbccdd');
    obj.size; // 4

    2. 操作方法

    1. Set.prototype.add(value)

    向Set集合添加元素

    参数:只能传入一个参数;参数可以是任意类型

    返回:添加元素后的Set实例

    const set = new Set();
    const result = set.add(1); 
    result.size; // 1 说明result是Set结构,且是添加元素之后的Set结构

    由返回值可知:add方法可以连写

    const set = new Set();
    const result = set.add(1).add('a').add(true);
    [...result]; //[1,'a',true]

    2. Set.prototype.delete(value)

    删除Set集合内的值

    参数:待删除的元素

    返回: 布尔值;表示是否删除成功

    const set = new Set();
    set.add(1).add(2).add(3);
    const result = set.delete(2);
    result; //  true  删除成功
    set.size; // 2
    const result2 = set.delete(5);
    result2; // false  Set集合不包括5,删除失败

    3. Set.prototype.has(value)

    判断Set数据集合中是否含有某元素

    参数: 待判断数据

    返回: 布尔值;表示是否包含该数据

    const set = new Set();
    set.add(1).add(2).add(3);
    set.has(1); // true
    set.has(5);  // false

    4. Set.prototype.clear()

    清空Set集合

    参数: 无

    返回:无(undefined)

    const set = new Set();
    set.add(1).add(2).add(3);
    set.size; //3
    set.clear();
    set.size; //0

    3. 遍历方法

    Set集合的遍历顺序是元素的插入顺序。和元素的数据类型无关。

    不同于对象的属性遍历顺序,根据属性遍历的先后顺序和属性的数据类型有关。

    1. Set.prototype.keys()

    同2

    2.Set.prototype.values()

    由于Set集合中只有value,没有key,所以默认keys和values都返回成员的值。

    返回:一个遍历器;遍历器成员是集合的值; set.keys()/set.values()返回值一摸一样

    values()方法是Set默认的[Symbol.iterator]属性对应的函数。

    const set = new Set([1,2,4]);
    const keys = set.keys(); // SetIterator
    for (let key of keys()) {
       console.log(key)
    }
    //  运行结果如下:
    1
    2
    4
    
    const values = set.values(); //SetIterator
    [...values]; //[1,2,4]   ...扩展运算符本质上调用的是for...of

    3. Set.prototype.entries()

     返回: 一个遍历器;遍历器每个成员是一个数组,数组含有相同的两个值。

    let set = new Set();
    set.add({a:1}).add(1).add(true);
    const entries = set.entries();
    for(let entity of entries) {
       console.log(entity);
    }
    // 运行结果如下:
    [{a:1}, {a:1}]
    [1,1]
    [true, true]

    4. Set.prototype.forEach(fn)

    用法和数组的 forEach基本一致

    const set = new Set([1,2,3]);
    set.forEach(function(value, key){ //value和key永远相等;数组中的key是index
       console.log(value,key);
    }, thisObj)
    // 运行结果如下:
    1,1
    2,2
    3,3

    3. Set应用

    1. 去重

    // 数组去重
    [...new Set([1,2,3,1,3,5])]; //[1,2,3,5]
    Array.from(new Set([1,2,3,1,3,5])); // [1,2,3,5]
    
    // Set的每个成员*2;先变为数组
    new Set([...new Set([1,2,3,1,3,5])].map(i => i*2)); // Set{2,4,6,10}
    new  Set(Array.from(new Set([1,2,3,1,3,5]), i => i*2)); //Set{2,4,6,10}

    2.求集合

    假如现有两个集合:let setA = new Set([1,2,3]);  let setB = new Set([1,3,5]);

    1. 并集

    const union = new Set([...setA, ...setB])
    // Set{1,2,3,5}

    2. 交集

    const intersect = new Set([...setA].filter(i => setB.has(i)));
    // Set{1,3}

    3. 差集

    const difference = new Set([...setA].filter(i => !setB.has(i)));
    // Set{2}  setA有;setB没有

    二. WeakSet数据结构

    1. 初始化

    大体和Set相同;也是一个构造方法。

    const ws = new WeakSet();

    可以通过数组或者类数组传参,元素类型必须是对象

    const ws = new WeakSet([{a:1}, {b:2}]);
    // 即数组内的每个元素必须是对象类型

    2. 和Set不同点:

    1. 元素只能是对象

    WeakSet数据集合中元素只能是对象(普通对象,函数,数组等);不能是原始类型的值。

    因此,add(value)的参数必须是对象,delete/has的参数数据类型可以是任意类型

    const ws = new WeakSet();
    // add(value)参数只能是对象
    ws.add({a:1}); // 普通对象
    ws.add([1,2]); // 数组
    // delele/ has不要求参数数据类型

    2. 不能遍历。

    1. 实例对象不含[Symbol.iterator]属性,不能遍历;

    所以,不能通过...或者Array.from将实例WeakSet数据转为数组

    2. 不含size属性;

    3. 没有forEach(), keys(), values(), entries()遍历方法。

    原因:

    该数据结构中的对象引用是弱引用,垃圾回收机制不考虑WeakSet对对象的引用。

    即某对象在WeakSet之外没有其他的引用,则该对象即使在WeakSet中有引用,也会被垃圾回收处理掉。

    因为元素对象的个数会根据外部引用情况而变化,所以不允许其遍历。

    3. 不能清空

    没有clear方法

    3. 应用

    对于引用的一些已经删除的DOM节点,已经不再需要,对于强引用的Set结构,

    这些垃圾数据不会被垃圾回收机制自动清理掉,仍占用内存, 需要手动delete删除来释放内存;

    但是WeakSet允许垃圾回收机制清理掉这些垃圾数据,释放内存。

    三. Map数据结构

    1. Map基础

    1. 含义

    Map是一种键值对的数据结构;不同于对象的键值只能是字符串和Symbol,

    Map结构的key可以是任意类型的数据。

    是一种更广义的值-值的映射。

    2. 初始化

    1. 初始化空值

    const map = new Map();
    const keyObj = {a:1};
    
    map.set(keyObj, 'content');

    2. 带参数初始化;可以传入元素是长度为2的数组或者具有iterator接口的数据类型

    const map = new Map([[1,2],[{a:2}, 5]]);
    // 等同于
    const set  = new Set([[1,2],[{a:2}, 5]]);
    const map = new Map(set)

    2. Map实例属性和方法

    1. 实例属性

    1.Map.prototype.size

    元素的个数

    const map = new Map();
    map.set('foo', true);
    map.set('bar', false);
    
    map.size // 2

    2. 操作方法

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

    向Map结构添加值

    返回map实例,可以链式写法;

    当添加相同的键名时,后面的键名对应的值会覆盖前面的。

    const map = new Map();
    map.set('foo', true).set('foo', 2);
    map.size; //1
    map.get('foo'); //2

    当key值是对象类型时,注意除非该对象赋值给一个遍历,使用时通过变量时会认为是同一个值;

    直接写对象(任何对象类型),都相当于新建了一个对象,它代表的是内存地址。

    // 赋值给遍历
    const map = new Map();
    const arrObj = ['a']
    
    map.set(arrObj, 555);
    map.get(arrObj) // 555
    
    // 直接使用
    map.set(['b'], 666);
    map.get(['b']) //undefined   set,get中的['b']是不同的内存地址地址对应的值
    // 即使值一样也对应不同的地址,对应Map的key来说是不同的值
    const k1 = ['c'];
    const k2 = ['c'];  //k1,k2值相同,但是内存地址不同
    map.set(k1, 5);
    map.get(k2)  //undefined

    2. Map.prototype.get(key)

    获取键名对应的值

    3. Map.prototype.has(key)

    判断某个键名是否存在

    4. Map.prototype.delete(key)

    删除某个键名对应的键值对

    5.Map.prototype.clear()

    清空整个Map数据结构

    3. 遍历方法

    1. Map.prototype.keys()

    返回键名的遍历器

    2. Map.prototype.values()

    返回值的遍历器

    3.Map.prototype.entries()

    返回键值对的遍历器

    Map结构的[Symbol.iterator]属性对应的是entries()方法

    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]  //Map函数生成的实例本身也可遍历
    // [[1,'one'], [2, 'two'], [3, 'three']]

    4.Map.prototype.forEach(fn)

    同Set

    4. 和其他数据结构互换

    1. Map->数组

    const map = new Map([[1,2],[{a:2}, 5]]);
    [...map]; // [[1,2],[{a:2}, 5]]

    2. 数组->Map

    数组必须满足每个成员都是长度为2的数组

    new Map([
      [true, 7],
      [{foo: 3}, ['abc']]
    ])

    3. Map->对象

    
    

    Object.fromEntries(new Map([
       [true, 7],
       [{foo: 3}, ['abc']]
    ])) //{true: 7, [object Object]: ['abc']}

    // 键值是对象时,会自动将对象转为对应的字符串

    4. 对象->Map

    new Map([...Object.entries({yes: true, no: false})])

    5. Map->JSON

    1)键名都是字符串

    先转为对象,再转JSON

    const set = new Map().set('yes', true).set('no', false);
    function toJSON() {
        return JSON.stringify(Object.fromEntries(set))
    }

    2)键名有非字符串

    转数组,再转JSON

    const set = new Map().set('yes', true).set('no', false);
    function toJSON() {
        return JSON.stringify([...set]))
    }

    6. JSON->Map

    5的逆运算

    都需要先JSON.parse()后再根据情况转换。

    四.WeakMap

    1. 初始化

    const wp = new WeakMap();
    wp.set({a:1}, 1) //键名必须是对象
    //
    const wp = new WeakMap([[{a:1}, 1],[{a:2}, 2]]) //键名必须是对象

    2.和Map差别

    1. 键名只能是对象

    2. 不能遍历。

    原因:第4项

    1. 没有size属性

    2.没有forEach,keys,values,entries的遍历方法

    3. 只有set,get,delete,has四个方法

    3.无法清空

    没有clear方法

    4.键名对应的对象不计入垃圾回收机制。

    3.应用-防止内存泄漏。

    键名对应的对象是弱引用,不计入垃圾回收机制。只是键名!!

    对于值是对象的情况,即使外部删除了引用,WeakMap内存仍然存在

    const wm = new WeakMap();
    let key = {};
    let obj = {foo: 1};
    
    wm.set(key, obj);
    obj = null;  // 将对象设置为null, 手动删除引用
    wm.get(key);// {foo: 1}内部仍存在

    首先,对象用在普通数据结构中,是强引用,不手动删除的话,会一直占用内存。

    const e1 = document.getElementById('foo');
    const e2 = document.getElementById('bar');
    const arr = [
      [e1, 'foo 元素'],
      [e2, 'bar 元素'],
    ]; 
    // e1, e2是两个NodeList对象,用于二维数组中,相当于被二维数组引用,就会占用内存存放;
    // arr之外其实e1,e2对应的DOM已经不存在,也不会自动被垃圾回收机制回收掉,只能手动删除,清空数组,arr.length = 0;

    而WeakMap结构是弱引用,如果出现上面例子中的情况,会自动被垃圾回收机制回收掉,释放内存。

    验证:

    使用process.memoryUsage()查看内存;
    
    // 首先打开node命令行
    node --expose-gc // 允许手动执行垃圾回收机制
    
    // 手动执行一次垃圾回收,保证获取的内存使用状态准确
    > global.gc();
    undefined
    
    // 查看内存占用的初始状态,heapUsed 为 4M 左右
    > process.memoryUsage();
    { rss: 21106688,
      heapTotal: 7376896,
      heapUsed: 4153936,
      external: 9059 }
    
    > let wm = new WeakMap();
    undefined
    
    // 新建一个变量 key,指向一个 5*1024*1024 的数组
    > let key = new Array(5 * 1024 * 1024);
    undefined
    
    // 设置 WeakMap 实例的键名,也指向 key 数组
    // 这时,key 数组实际被引用了两次,
    // 变量 key 引用一次,WeakMap 的键名引用了第二次
    // 但是,WeakMap 是弱引用,对于引擎来说,引用计数还是1
    > wm.set(key, 1);
    WeakMap {}
    
    > global.gc();
    undefined
    
    // 这时内存占用 heapUsed 增加到 45M 了
    > process.memoryUsage();
    { rss: 67538944,
      heapTotal: 7376896,
      heapUsed: 45782816,
      external: 8945 }
    
    // 清除变量 key 对数组的引用,
    // 但没有手动清除 WeakMap 实例的键名对数组的引用
    > key = null;
    null
    
    // 再次执行垃圾回收
    > global.gc();
    undefined
    
    // 内存占用 heapUsed 变回 4M 左右,
    // 可以看到 WeakMap 的键名引用没有阻止 gc 对内存的回收
    > process.memoryUsage();
    { rss: 20639744,
      heapTotal: 8425472,
      heapUsed: 3979792,
      external: 8956 }
    垃圾回收机制

    示例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);
    //一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

    示例二:部署类的私有属性

    // Countdown类的两个内部属性_counter和_action,是实例的弱引用,所以如果删除实例,它们也就随之消失,不会造成内存泄漏
    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
    View Code

    详情参考

  • 相关阅读:
    设计模式-状态模式(25)
    设计模式-访问者模式(24)
    设计模式-观察者模式(22)
    设计模式-中介者模式(21)
    设计模式-行为型模式小结(20)
    设计模式-迭代器模式(19)
    Tomcat安装
    MySQL单表查询
    MySQL表操作
    MySQL表的完整性约束
  • 原文地址:https://www.cnblogs.com/lyraLee/p/11553167.html
Copyright © 2020-2023  润新知