一、ES6新增数据结构(set/map)

(1)set/map概念

两个都是es6新增的构造函数:

  1. set:类似于数组,只能存值没有键,值是唯一的
  2. map:类似于对象,可以存键和值,键名不限于字符串,键和值一一对应

set、map、Promise、Proxy四个无法通过babel编译语法降级(poly feel、babel poly feel)

(2)set/map深入

  1. set
  1. 原型
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
  1. 参数
    参数是具备 iterator 接口的数据结构:[],类数组
var set = new Set([5,7]); 
console.log(set);
// Set(2) {5, 7}
// [[Entries]]
// 0: 5
// 1: 7
// size: 2
// [[Prototype]]: Set
  1. 声明方式
//方法一
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}
  1. 值是唯一的
var set = new Set([1,2,3,4,5,5,5,5]);
console.log(set); //Set(5) {1,2,3,4,5}
  1. 特殊值
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
  1. 操作方法
  1. 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
  1. size
    返回当前长度
console.log(set.size); // 2
  1. delete
    清除某个值,返回值是布尔值,操作是实时的
console.log(set.delete(y));  // false
console.log(set);
// Set(2) {{…}, {…}}
// [[Entries]]
// 0:
// value: {id: 1}
// [[Prototype]]: Set
  1. 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
  1. has
    判断是否有指定值,返回值是布尔值
console.log(set.has(x)); // true
  1. 遍历方法
  1. 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]
}
  1. 迭代器
    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
}
  1. 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'}
  1. 使用场景
  1. 拓展运算符-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']
  1. 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
  1. filter-过滤成员
let set2 = new Set([...set].filter(x => (x%2) == 0))
console.log(set2); //{2,4,6}
  1. 交集并集差集
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}
  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));
  1. map
  1. 原型
    和set的原型基本一致,只是多了set、get方法,主要用于存取键值,而set本身没有键,所以不需要这两个方法
  2. 与对象对比
  1. 普通对象
    键和值不能实现一一对应
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从而发生覆盖
  1. 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
  1. 参数
  1. 参数是具备 iterator 接口的数据结构:[],类数组
  2. 参数是以键值对存在的二维数组 [['键名', '键值'], ...]
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
  1. 声明方式
// 方法一
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')
  1. 覆盖问题
  1. 原始值
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
  1. 引用值
// 指针不同所以访问不到
map.set([5],555);
console.log(map.get([5])); // undefined
// 指针相同所以可以访问
var arr = [5];
map.set(arr,555); 
console.log(map.get(arr)); //555

原始值在栈里面存储的是地址,引用值在栈里面存储的是指向堆的指针

  1. 操作方法
  1. set
    添加成员,返回值set实例本身
const m  = new Map();
// 链式调用
m.set(1,"foo").set(2,"bar");  
console.log(m); // Map(2) {1 => 'foo', 2 => 'bar'}
  1. get
    获取成员,返回值set实例本身
m.get(1); //'foo'
  1. size
    获取成员长度
var x = {id:1},
    y = {id:2};
m.set(x,"foo");
m.set(y,"bar") ;  
cosnole.log(m.size); //2
  1. 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"}
  1. 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) {}
  1. has
    判断成员,返回值是布尔值
var x = {id:1},
    y = {id:2};
m.set(x,"foo");
m.set(y,"bar") ;  
cosnole.log(m.has(x)); //true
  1. 遍历方法
  1. 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']
  1. 迭代器
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"
  1. 使用场景
  1. 拓展运算符-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)
  1. 拓展运算符-数组转化为map 结构
const map = new Map([
    [true,7],
    [{foo:3},['abc']]
]);
console.log(map); // Map(2) {true => 7, {…} => Array(1)}
  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'}
  1. 拓展运算符-对象转化为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

  1. 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); //[]
  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); //[]
  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