一、ES6新增数据结构(set/map)
(1)set/map概念
两个都是es6新增的构造函数:
- set:类似于数组,只能存值没有键,值是唯一的
- map:类似于对象,可以存键和值,键名不限于字符串,键和值一一对应
set、map、Promise、Proxy四个无法通过babel编译语法降级(poly feel、babel poly feel)
(2)set/map深入
-
set
-
原型
var set = new Set(); console.log(set); // Set(0) {size: 0} // [[Entries]] // No properties // size: 0 // [[Prototype]]: Set // add: ƒ add() // clear: ƒ clear() // constructor: ƒ Set() // delete: ƒ delete() // entries: ƒ entries() // forEach: ƒ forEach() // has: ƒ has() // keys: ƒ values() // size: (...) // values: ƒ values() // Symbol(Symbol.iterator): ƒ values() // Symbol(Symbol.toStringTag): "Set" // get size: ƒ size() // [[Prototype]]: Object
-
参数
参数是具备 iterator 接口的数据结构:[],类数组
var set = new Set([5,7]); console.log(set); // Set(2) {5, 7} // [[Entries]] // 0: 5 // 1: 7 // size: 2 // [[Prototype]]: Set
-
声明方式
//方法一 var set = new Set([1,2,3]); console.log(set); //Set(3) {1,2,3} //方法二 var set = new Set(); set.add(1).add(2).add(3); console.log(set); //Set(3) {1,2,3}
-
值是唯一的
var set = new Set([1,2,3,4,5,5,5,5]); console.log(set); //Set(5) {1,2,3,4,5}
-
特殊值
var set = new Set([undefined,undefined,null,null,5,'5',true,1,NaN,NaN,{},{},[],[]]) console.log(set); // Set(11) {undefined, null, 5, '5', true, …} // [[Entries]] // 0: undefined // 1: null // 2: 5 // 3: "5" // 4: true // 5: 1 // 6: NaN 注意:1. 在set里面NaN是等于NaN的 // 7: Object 2. {},[]是构造器实例出来的唯一引用,所以不等 // 8: Object // 9: Array(0) // 10: Array(0) // size: 11 // [[Prototype]]: Set
-
操作方法
-
add
追加数据,返回值是set实例var set = new Set(); var x = {id:1}; var y = {id:2}; //一般用法 set.add(x); set.add(y); //链式调用 set.add(x) // 返回值是set实例本身 .add(y) .add(x); // 引用相同,无法添加 console.log(set); // Set(2) {{…}, {…}} // [[Entries]] // 0: // value: {id: 1} // 1: // value: {id: 2} // size: 2 // [[Prototype]]: Set
-
size
返回当前长度console.log(set.size); // 2
-
delete
清除某个值,返回值是布尔值,操作是实时的console.log(set.delete(y)); // false console.log(set); // Set(2) {{…}, {…}} // [[Entries]] // 0: // value: {id: 1} // [[Prototype]]: Set
-
clear
清空所有的值,返回值是undefined,操作是实时的// 注意打印位置: 发现输出结果一致,说明操作是实时的 console.log(set); // Set(0) {size: 0} // [[Entries]] // No properties // size: 0 // [[Prototype]]: Set set.clear() console.log(set); // Set(0) {size: 0} // [[Entries]] // No properties // size: 0 // [[Prototype]]: Set
-
has
判断是否有指定值,返回值是布尔值console.log(set.has(x)); // true
-
-
遍历方法
-
keys/values/entries
遍历键,值,键值对数组,返回值是迭代器对象
set 不存在键名,故键名和键值是一致的
let set = new Set([1, 2, 3, 4, 5, 6, 7]); console.log(set.keys()); // SetIterator {1, 2, 3, 4, 5, …} console.log(set.values()); // SetIterator {1, 2, 3, 4, 5, …} console.log(set.entries()); // SetIterator {1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, …} for (let i of set.keys()) { console.log(i); // 1 2 3 4 5 6 7 } for (let i of set.values()) { console.log(i); // 1 2 3 4 5 6 7 } for (let i of set.entries()) { console.log(i); // [1,1] [2,2] [3,3] [4,4] [5,5] [6,6] [7,7] }
-
迭代器
set对象上也部署了迭代器,可通过for…of遍历// 判断遍历的是键还是值,结果是一致的,说明实际上是在调用set实例上的values方法 console.log(Set.prototype[Symbol.iterator] === Set.prototype.values);//true for(let values of set){ console.log(values); //1 2 3 4 5 6 7 }
-
forEach
遍历成员(键、值、set结构)let set = new Set(['a', 'b', 'c', 'd']); set.forEach((value, keys, s) => { console.log(value, keys, s) }) // a a Set(4) {'a', 'b', 'c', 'd'} // b b Set(4) {'a', 'b', 'c', 'd'} // c c Set(4) {'a', 'b', 'c', 'd'} // d d Set(4) {'a', 'b', 'c', 'd'}
-
-
使用场景
-
拓展运算符-set 结构转化为数组,去重
去重最简单的方式就是收集到set当中,再展开到一个数组里
// ...能展开具备迭代器接口的数据结构 let set = new Set(['a','b','c','d','e','f','g']); console.log(...set); //a b c d e f g let set = new Set(['a','b','c','d','e','f','g']; console.log([...set]); //['a','b','c','d','e','f','g']
-
map-操作成员
let set = new Set([1,2,3,4,5,6,7]) let set1 = new Set([...set].map(value => value*2)); console.log(set1); // Set(7) {2, 4, 6, 8, 10, …}
难点
var arr = [1,2,3,4] var arr1 = arr.map(parseInt); console.log(arr1);//[1,NaN,NaN,NaN] //原理就是map里的参数 value, index,默认传给了parseInt var arr1 = arr.map((value,idx)=> console.log(value,idx)); //1 0 //2 1 //3 2 //4 3 //那么就代表着,第二个参数转换进制,无法转换就是NaN parseInt(1,0)parseInt(2,1)parseInt(3,2)parseInt(4,3); let set2 = new Set([[...set].map(parseInt)]); //这里是数组里嵌套数组,所以size是1 console.log(set2); // Set(1) {Array(7)} // [[Entries]] // 0: Array(7) // value: Array(7) // 0: 1 // 1: NaN // 2: NaN // 3: NaN // 4: NaN // 5: NaN // 6: NaN // length: 7 // [[Prototype]]: Array(0) // size: 1 // [[Prototype]]: Set
-
filter-过滤成员
let set2 = new Set([...set].filter(x => (x%2) == 0)) console.log(set2); //{2,4,6}
-
-
交集并集差集
let a = new Set([1, 2, 3]); let b = new Set([4, 3, 2]); //并集 let union = new Set([...a, ...b]); console.log(union); //Set(4) {1, 2, 3, 4} //交集 let intersect = new Set([...a].filter(x => b.has(x))); console.log(intersect); //Set(2) {2, 3} //差集 let difference = new Set([...a].filter(x => !b.has(x))); console.log(difference) //Set(1) {1}
-
映射新的结构
//方法一 let set = new Set([1,2,3]); let set1 = new Set([...set].map(value => value*2)); //写法二 let set = new Set([1,2,3]); let set1 = new Set(Array.from(set,value=>value*2));
-
-
map
-
原型
和set的原型基本一致,只是多了set、get方法,主要用于存取键值,而set本身没有键,所以不需要这两个方法
-
与对象对比
-
普通对象
键和值不能实现一一对应
var m = {} var x = {id:1}, y = {id:2}; m[x] = "foo"; // => m['object Object'] = "foo"; m[y] = "bar"; // => m['object Object'] = "bar"; console.log(m); //{[object Object]:"bar"} console.log(m[x]); //bar console.log(m[y]); //bar //当键名是对象的时候会调用toString从而发生覆盖
-
map结构
键和值一一对应
let m = new Map(); var x = {id:1}, y = {id:2}; m.set(x,'foo'); // Tips: 1.这里是通过set设置的 m.set(y,'bar'); console.log(m.get(x));//foo 2. 这里通过get访问的 console.log(m.get(y));//bar
-
-
参数
- 参数是具备 iterator 接口的数据结构:[],类数组
- 参数是以键值对存在的二维数组
[['键名', '键值'], ...]
let m = new Map([ ['name', 'zhangsan'], ['title', 'lisi'] ]) console.log(m) // Map(2) {'name' => 'zhangsan', 'title' => 'lisi'} // [[Entries]] // 0: {"name" => "zhangsan"} // 1: {"title" => "lisi"} // size: 2 // [[Prototype]]: Map
-
声明方式
// 方法一 let m = new Map([ ['name', 'wangwu'], ['title', 'zhaoliu'] ]) //传参实现原理: var items = [['name','wagwu'],['title','zhaoliu']] let m = new Map(); items.forEach(([key,value]) => m.set(key,value)) // 方法二 let m = new Map(); map.set('name','zhangsan') map.set('title','lisi')
-
覆盖问题
-
原始值
const map = new Map(); // 地址相同所以会覆盖 map.set(1, 'foo'); // Map(1) {1 => 'foo'} map.set(1, 'bar'); // Map(1) {1 => 'bar'} // 认定-0和+0是全等的,只是Object.is判断是false map.set(-0,123); console.log(map.get(+0)) //123 // 不会有隐式转换 map.set(true,1); map.set('true',2); console.log(true); // 1 // 键名也可以是undefined和unll map.set(undefined,1) map.set(null,2) log(map.get(undefined)); //1 log(map.get(null)); //2 // 认定NaN是等于NaN的,只是Object.is判断是true map.set(NaN,123); log(map.get(NaN));//123
-
引用值
// 指针不同所以访问不到 map.set([5],555); console.log(map.get([5])); // undefined // 指针相同所以可以访问 var arr = [5]; map.set(arr,555); console.log(map.get(arr)); //555
原始值在栈里面存储的是地址,引用值在栈里面存储的是指向堆的指针
-
-
操作方法
- set
添加成员,返回值set实例本身const m = new Map(); // 链式调用 m.set(1,"foo").set(2,"bar"); console.log(m); // Map(2) {1 => 'foo', 2 => 'bar'}
- get
获取成员,返回值set实例本身m.get(1); //'foo'
- size
获取成员长度var x = {id:1}, y = {id:2}; m.set(x,"foo"); m.set(y,"bar") ; cosnole.log(m.size); //2
- delete
删除成员,返回值是布尔值,操作是实时的var x = {id:1}, y = {id:2}; m.set(x,"foo"); m.set(y,"bar") ; cosnole.log(m.delete(x)); //true m.delete(x); // Map(1) {{...} => "bar"}
- clear
清空成员,返回值undefined,操作是实时的var x = {id:1}, y = {id:2}; m.set(x,"foo"); m.set(y,"bar") ; cosnole.log(m.clear()); //undefined m.clear(); // Map(0) {}
- has
判断成员,返回值是布尔值var x = {id:1}, y = {id:2}; m.set(x,"foo"); m.set(y,"bar") ; cosnole.log(m.has(x)); //true
- set
-
遍历方法
- keys/values/entries
var x = {id:1}, y = {id:2} m.set(x,"foo") m.set(y,"bar") for(let keys of m.keys()){ console.log(keys) } // {id:1} // {id:2} for(let keys of m.values()){ console.log(values) } // foo // bar for(let keys of m.entries()){ console.log(entries) } // [{...},'foo'] // [{...},'bar']
- 迭代器
console.log(m[Symbol.iterator] === m.entries) //true 说明实际在调用map实例上的entries方法 for(let i of m){ console.log(i) } // [{...},"foo"] // [{...},"bar"] // 模式匹配 for(let [key,values] of m){ console.log(key,values) } // {id:1}"foo" // {id:2}"bar"
- keys/values/entries
-
使用场景
-
拓展运算符-map 结构转化为数组
const myMap = new Map(); myMap.set(true,7) .set({foo:3},['abc']); console.log(myMap); //Map(2) {true=>7, {...}=>Array(1)} console.log([...myMap]); // (2) [Array(2), Array(2)] // 0: (2) [true, 7] // 1: (2) [{...}, Array(1)] // length: 2 // __proto__: Array(0)
-
拓展运算符-数组转化为map 结构
const map = new Map([ [true,7], [{foo:3},['abc']] ]); console.log(map); // Map(2) {true => 7, {…} => Array(1)}
-
拓展运算符-map 结构转化为对象(条件:键名为字符串)
const myMap = new Map(); myMap.set({},7) .set({},'abc'); function strMapToObj(strMap){ let obj = Object.create(null); for(let [key,val] of strMap.entries()){ obj[key] = val; } return obj; } console.log(strMapToObj(myMap)); //{true: 7, a: 'abc'}
-
拓展运算符-对象转化为map 结构
function objToStrMap(obj){ let map = new Map(); for(let key of Object.keys(obj)){ map.set(key,obj[key]); } return map } console.log(objToStrMap({true:7,no:false})); //Map(2) {'true' => 7, 'a' => 'abc'}
对象没有部署迭代器接口,为什么这里还能使用for…of呢?
这是因为Object.keys把它变成了键名构成的数组
-
-
(3)set/map对比array/object
-
set对比array
let set = new Set(); let arr = new Array; //增加 set.add({t:1}); //Set(1) {{t:1}} arr.push({t:1}); //[{t:1}] //查询 set.has({t:1}); //false 因为不是同一个指针 arr.find(item => item.t); //{t: 1} //修改 set.forEach(item => item.t ? item.t = 2:""); //Set(1) {{t:2}} arr.forEach(item => item.t ? item.t = 2:""); //[{t:2}] //删除 set.forEach(item => item.t ? set.delete(item):''); //Set(0) {size: 0} let index = arr.findIndex(item => item.t); arr.splice(index,1); //[]
-
map对比array
let map = new Map(); let arr = new Array(); //增加 map.set('t',1); //Map(1) {'t' => 1} arr.push({'t':1}); //[{t,1}] //查询 map.has('t'); //true arr.find(item => item.t); //{t: 1} //修改 map.set('t',2); //Map(1) {'t' => 2} arr.forEach(item => item.t ? item.t = 2 : ''); //[{t,2}] //删除 map.delete('t'); //Map(0) {size: 0} let index = arr.findIndex(item => item.t); arr.splice(index,1); //[]
-
map/set对比object
let map = new Map(); let set = new Set(); let item = {t:1} let obj= {} //增加 map.set('t',1); //Map(1) {'t' => 1} set.add(item); //Set(1) {{t:1}} obj['t'] = 1; //{t:1} //查询 map.has('t'); //true set.has(item); //true 't' in obj; //true obj.hasOwnProperty('t') //true //修改 map.set('t',2); //Map(1) {'t' => 2} item.t = 2; //Set(1) {{t:2}} obj['t'] = 2; //{t:2} //删除 map.delete('t'); //Map(0) {size: 0} set.delete(item); //Set(0) {size: 0} delete obj['t']; //{}
(4)结论
map/set都有迭代器接口,底层优化比array/object做得更好,结构操作比array/object更优雅,若对数据结构要求比较高,且保证数据唯一性的话就用map/set而不用array/object