(九)Set|(九)Set 和 Map 数据结构

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 // bluefor (let item of set.values()) { console.log(item); } // red // green // bluefor (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 // blueset = 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); // falsews.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)// truem.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 (...........); }

核心代码
【(九)Set|(九)Set 和 Map 数据结构】文件位置:/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); } }

    推荐阅读