美文网首页
(九)Set 和 Map 数据结构

(九)Set 和 Map 数据结构

作者: 做最棒的 | 来源:发表于2018-09-11 20:41 被阅读0次

    Set集合是一种无重复元素的列表,开发者们一般不会像访问数组元素那样逐一访问每个元素,通常的做法是检测给定的值在某个集合中是否存在
    Map集合内含多组键值对,集合中每个元素分别存放着可访问的健名和对它对应的值,Map集合经常被用于缓存频繁取用的数据
    Set和Map都是通过Object.is()实现排重的。

    1、Set

    1) Set 本身是一个构造函数,用来生成 Set 数据结构
    2) 它类似于数组,但是成员的值都是唯一的,没有重复的值。

    Set 结构的实例有以下属性。

    • Set.prototype.constructor:构造函数,默认就是Set函数。
    • Set.prototype.size:返回Set实例的成员总数
    const set = new Set([1, 2, 3, 4, 4]);
    console.log(Set.prototype.constructor)
    console.log(set.size)
    

    四个操作方法。

    • add(value):添加某个值,返回Set结构本身
    • delete(value):删除某个值,返回一个布尔值,表示删除是否成功
    • has(value):返回一个布尔值,表示该值是否为Set的成员
    • clear():清除所有成员,没有返回值
    const set = new Set([1, 2, 3, 4, 4]);
    set.add(5);
    set.add(6).add(7);
    // 为什么可以set.add(6).add(7);
    // 作业实现一个这样的代码
    console.log(set);
    console.log(set.has(7));
    console.log(set.delete(3));
    console.log(set);
    console.log(set.clear());
    console.log(set);
    console.log(set.size)
    

    Set的遍历

    Set 结构的实例有四个遍历方法,可以用于遍历成员。

    • keys():返回键名的遍历器
    • values():返回键值的遍历器
    • entries():返回键值对的遍历器
    • forEach():使用回调函数遍历每个成员
    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"]
    
    let set = new Set(['red', 'green', 'blue']);
    
    for (let x of set) {
      console.log(x);
    }
    // red
    // green
    // blue
    
    set = new Set([1, 4, 9]);
    set.forEach((value, key) => console.log(key + ' : ' + value))
    // 1 : 1
    // 4 : 4
    // 9 : 9
    
    forEach
    //数组 forEach
    const items = ['item1', 'item2', 'item3'];
    const copy = [];
    
    items.forEach(function(item){
      copy.push(item)
    });
    
    //set forEach
    

    forEach 在数组上的介绍
    演示forEach不同

    使用 Set 可以很容易地实现并集(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}
    

    如果想在遍历操作中,同步改变原来的 Set 结构,目前没有直接的方法,但有两种变通方法。一种是利用原 Set 结构映射出一个新的结构,然后赋值给原来的 Set 结构;另一种是利用Array.from方法。

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

    2、WeakSet

    1) WeakSet 结构与 Set 类似,也是不重复的值的集合
    2) WeakSet 的成员只能是对象,而不能是其他类型的值

    WeakSet 结构有以下三个方法。

    • WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
    • WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
    • WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。
    1) WeakSet 没有size属性,没有办法遍历它的成员
    2) WeakSet 不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet 的一个用处,是储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏。
    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
    

    3、Map

    ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

    const m = new Map();
    const o = {p: 'Hello World'};
    
    m.set(o, 'content')
    
    //下面两个结果一样吗
    m.get(o) // "content"
    m.get({p: 'Hello World'})
    
    m.has(o) // true
    m.delete(o) // true
    m.has(o) // false
    
    // 下面这种模式初始化不行
    // const map2 = new Map({
    //  'name': '张三',
    //  'title': 'Author'
    // });
    
    实例的属性和操作方法

    1)size 属性
    size属性返回 Map 结构的成员总数。

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

    2)set(key, value)

    const m = new Map();
    
    m.set('edition', 6)        // 键是字符串
    m.set(262, 'standard')     // 键是数值
    m.set(undefined, 'nah')    // 键是 undefined
    

    set方法返回的是当前的Map对象,因此可以采用链式写法。

    let map = new Map()
      .set(1, 'a')
      .set(2, 'b')
      .set(3, 'c');
    

    3)get(key)
    get方法读取key对应的键值,如果找不到key,返回undefined

    const m = new Map();
    
    const hello = function() {console.log('hello');};
    m.set(hello, 'Hello ES6!') // 键是函数
    
    m.get(hello)  // Hello ES6!
    

    4)has(key)
    has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。

    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
    

    5)delete(key)
    delete方法删除某个键,返回true。如果删除失败,返回false。

    const m = new Map();
    m.set(undefined, 'nah');
    m.has(undefined)     // true
    
    m.delete(undefined)
    m.has(undefined)       // false
    

    6)clear()
    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 的所有成员。
    需要特别注意的是,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"
    
    与其他数据结构的互相转换

    (1)Map 转为数组

    const myMap = new Map()
      .set(true, 7)
      .set({foo: 3}, ['abc']);
    [...myMap]
    // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
    

    (2)数组 转为 Map

    将数组传入 Map 构造函数,就可以转为 Map。

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

    (3)Map 转为对象

    如果所有 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 }
    

    如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。

    (4)对象转为 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}
    

    (5)Map 转为 JSON

    Map 转为 JSON 要区分两种情况。一种情况是,Map 的键名都是字符串,这时可以选择转为对象 JSON。

    function strMapToJson(strMap) {
      return JSON.stringify(strMapToObj(strMap));
    }
    
    let myMap = new Map().set('yes', true).set('no', false);
    strMapToJson(myMap)
    // '{"yes":true,"no":false}'
    

    另一种情况是,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"]]]'
    

    (6)JSON 转为 Map

    JSON 转为 Map,正常情况下,所有键名都是字符串。

    function jsonToStrMap(jsonStr) {
      return objToStrMap(JSON.parse(jsonStr));
    }
    
    jsonToStrMap('{"yes": true, "no": false}')
    // Map {'yes' => true, 'no' => false}
    
    

    但是,有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。这往往是 Map 转为数组 JSON 的逆操作。

    function jsonToMap(jsonStr) {
      return new Map(JSON.parse(jsonStr));
    }
    
    jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
    // Map {true => 7, Object {foo: 3} => ['abc']}
    

    4、 WeakMap

    自己看,下次分享的小伙伴分享这块

    5、 为什么需要Set和Map

    长久以来,数组一直是JavaScript唯一的集合类型。(不过,一些开发者认为非数组对象也是集合,只不过是键值对集合,他们的用途和数组完全不同)。JavaScript数组功能与其他语言中的一样,但在ECMAScript6标准制定以前,由于可选的集合类型有限,数组使用的又是索引,因而经常被用于创建队列和栈。如果开发者们需要使用非数值型索引,就会用非数组对象创建所需的数据结构,而这就是Set集合与Map集合的早起实现。

    Set集合是一种无重复元素的列表,开发者们一般不会像访问数组元素那样逐一访问每个元素,通常的做法是检测给定的值在某个集合中是否存在
    这些就是数组和Set,Map本质的区别

    6、 Map实战

    实现多tab切换保存内容方案

    多个tab切换的时候,要保留上次操作的历史。需要保留每个模块的state。这样要求模块入口的index中state及子模块的state是相互贯通的。目前有这个功能的模块包括【任务中心】、【班级管理】、【学员管理】、【师资管理】、【权限管理】、【课程管理】、【辅导班管理】。

    实现方案介绍 (中心思想)

    每个tab的path不一样,所以以path为key,state为value建立map。由于每个模块有多个子模块的state也需要维护,所以扩展为采用path+tag为key。tag的值原则上可以随便定义,最好以数字拓展,格式如'_1','_2','_3'等等。【权限管理】树模块有tag='_tree'。【辅导班管理】有tag='_1',可以根据子tab的type确定。

    使用方法

    1、 合并新state和已保存的state 。需要在constructor函数里添加一行代码:this.state = util.BossTabMap.getSaveState(tag, this.state);必须在this.state初始化之后。如果是路由首页,可以这样使用:this.state = util.BossTabMap.getSaveState('', this.state);

    constructor(props) {
            super(props);
            ....................
            this.state = {
                list: [],
                pager: defaultPager,
                searchParams: searchParams,
                loading: false,
            };
            ............
            
            this.state = util.BossTabMap.getSaveState('',this.state);//无tag
            或:
            const tag='_1';
            this.state = util.BossTabMap.getSaveState(tag, this.state);
        }
    

    2、 保存当前state。只有render中的state才是最后一次操作保留下来的,所以,在rander中添加代码: util.BossTabMap.addSaveState('', this.state);util.BossTabMap.addSaveState(tag, this.state);

    render() {
            const self = this;
            ..........
            util.BossTabMap.addSaveState('_tree', this.state);
            return (
                <div className="department-tree left">
                    ...........
                </div>
            );
        }
    

    核心代码

    文件位置:/new_boss_fe/src/spa/common/util/util.jsx

        /*
         * tab状态保存。
         * 获取已保存的state并产生新state:this.state = UTIL.BossTabMap.getSaveState('',this.state);
         * 添加已保存state:util.BossTabMap.addSaveState('',this.state);
         * 删除记录:tag多样性的原因。删除时要注意。
         */
        ..........................
        BossTabMap: {
            tabsMap: new Map(),// 用来保存state
            tagArr: ['_tree', '_1', '_2', '_3', '_4', '_5'],//保存tag
            getTag: function (path) {
                // 根据path获取tag
                const self = this;
                const curTag = this.tagArr.filter(function (tag) {
                    return self.tabsMap.has(path + tag);
                });
                return curTag[0] || '';
            },
            setTag: function (vtag) {
                // 设置tag
                var tag = vtag || '';
                this.tagArr.push(tag);
            },
            getSaveState: function (tag, state) {
                // 获取已保存的state
                const path = location.hash + tag;
                if (this.tabsMap.has(path)) {
                    return Object.assign(state, this.tabsMap.get(path));
                }
                return state;
            },
            addSaveState: function (tag, state) {
            // 添加state
                const path = location.hash + tag;
                this.tabsMap.set(path, state);
            },
            delSaveTabs: function (path) {
                    //关闭对应的tab时候,需要从map中去掉保存的state。
                    
                // 有tag的path如何清除呢?
                // const path = location.hash;
                // tag   _tree,'_1','_2' 有一些path 人为加了tag
                const self = this;
                const tag = this.getTag(path);
                // console.log('delSaveTabs..tabsMap..start=', this.tabsMap);
                if (this.tabsMap.has(path)) {
                    self.tagArr.forEach((ctag) => {
                        // console.log('ctag=', (path+ctag));
                        const tpath = path + ctag;
                        if (self.tabsMap.has(tpath)) {
                            // console.log('tpath=', tpath);
                            self.tabsMap.delete(tpath);
                        }
                    })
                    self.tabsMap.delete(path);
                }
                // console.log('delSaveTabs..tabsMap..end=', this.tabsMap);
            },
            has: function (path) {
                if (this.tabsMap.has(path)) {
                    return true;
                }
                return false;
            },
            add: function (path, state) {
                this.tabsMap.set(path, state);
            },
            get: function (path) {
                return this.tabsMap.get(path);
            },
            del: function (path) {
                this.tabsMap.delete(path);
            }
        }
    

    相关文章

      网友评论

          本文标题:(九)Set 和 Map 数据结构

          本文链接:https://www.haomeiwen.com/subject/mqlpgftx.html