数据类型
Map&Set
Map
var m = {};var x = { id: 1 }var y = { id: 2 };m[x] = 'foo';m[y] = 'bar';//这里看出{id:1}转为字符串[object Object]console.log(Object.prototype.toString({ id: 1 })); //[object Object]console.log(m); //{[object Object]: "bar"}//一样的键名,第二次会覆盖第一次的操作console.log(m[x]); //barconsole.log(m[y]); //bar
以上可以看出传统的对象处理方式并不友好,当键名是一个对象的时候,它是调用原型上的toString()方法,转完之后并不能实现键名键值一一对应,ES6提供的新的数据结构Map()来解决以上问题
Map()存放的是对象,有键名和键值,且键名不限于字符串,也可以是对象,也可实现一一对应的关系
let m = new Map();var x = { id: 1 };var y = { id: 2 };//设置键名和键值m.set(x, 'foo');m.set(y, 'bar');//获取键值console.log(m.get(x)); //fooconsole.log(m.get(y)); //barconsole.log(m);/*** Map(2) {{…} => "foo", {…} => "bar"}* [[Entries]]* 0: {Object => "foo"}* 1: {Object => "bar"}* size: 2* __proto__: Map*/
Map()数据也具备iterator接口的数据结构
//写法一://参数必须以数组的形式出现双源的数据结构let m = new Map([['name', 'zhangsan'],['title', 'lisi']]);//写法二:let m2 = new Map();m2.set('name', 'zhangsan');m2.set('title', 'lisi');console.log(m);/*** Map(2) {"name" => "zhangsan", "title" => "lisi"}* [[Entries]]* 0: {"name" => "zhangsan"}* 1: {"title" => "lisi"}* size: 2* __proto__: Map*/
//模拟将键名和键值遍历到map数据里var items = [['name', 'wangwu'],['title', 'zhaoliu']];let m = new Map();items.forEach(([key, value]) => m.set(key, value));console.log(m);/*** Map(2) {"name" => "wangwu", "title" => "zhaoliu"}* [[Entries]]* 0: {"name" => "wangwu"}* key: "name"* value: "wangwu"* 1: {"title" => "zhaoliu"}* key: "title"* value: "zhaoliu"* size: 2* __proto__: Map*/
//键值相同覆盖的问题const map = new Map();map.set(1, 'foo');map.set(1, 'bar');console.log(map.get(1)); //bar
//键值为NaNconst map = new Map();map.set(NaN, 123);console.log(map.get(NaN)); //123console.log(NaN === NaN); //falseconsole.log(Object.is(NaN, NaN)); //true
//如何将Map数据转为数组const myMap = new Map();//设置键名键值myMap.set(true, 7);myMap.set({foo: 3}, ['abc']);console.log(myMap);//Map(2) {true => 7, {foo: 3} => ['abc']}console.log([...myMap]);//[[true, 7], [{foo: 3}, ["abc"]]]
//如何将Map数据转为对象const myMap = new Map();myMap.set(true, 7);myMap.set('a', 'abc');function strMapToObj(strMap) {//Object.create(指向原型)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 strMap = new Map();for (let key in obj) {strMap.set(key, obj[key])}return strMap;}console.log(objToStrMap({true: 7,'no': false}));//Map(2) {"true" => 7, "no" => false}
Set
特点:成员唯一的数组
场景:利用set的唯一性对数据进行去重,然后将set转化为数组提高效率
//参数只能是具备iterator接口的数据结构var set = new Set([1, 2, 3, 4, 5]);set.add(5);set.add(7);console.log(set);/*** Set(6) {1, 2, 3, 4, 5, …}* [[Entries]]* 0: 1* 1: 2* 2: 3* 3: 4* 4: 5* 5: 7* size: (...)* __proto__: Set*/
//简单判断特殊的值让set识别是否唯一的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* 7: Object* 8: Object* 9: Array(0)* 10: Array(0)* size: (...)* __proto__: Set*/
方法:add()/delete()/clear()/has()
//add()添加值,返回的是set结构本身var set = new Set();var x = {id: 1},y = {id: 2}set.add(x);set.add(y);console.log(set);/*** Set(2) {{…}, {…}}* [[Entries]]* 0:* value: {id: 1}* 1:* value: {id: 2}* size: 2* __proto__: Set*/
//delete()删除指定一个//返回值为布尔值 true可删/false没数据可删console.log(set.delete(y));/*** Set(1) {{…}}* [[Entries]]* 0:* value: {id: 1}* size: 1* __proto__: Set*/
//clear()全部清空//返回值为undefinedconsole.log(set.clear());/*** Set(0) {}* [[Entries]]* No properties* size: 0* __proto__: Set*/
//has()判断当前是否含有指定值//返回值为布尔值 true有该值/false没有该值console.log(set.has(x));
//set特征:实时监控数据var set = new Set([1, 2]);console.log(set);set.clear();//注意:打印是在清除之前的,但仍然没有数据显示,说明set实时监控数据/*** Set(2) {1, 2}* [[Entries]]* No properties* size: 0* __proto__: Set*/
遍历方法:keys()/values()/entries()/forEach()
//返回值是迭代器对象let set = new Set([1, 2, 3]);console.log(set.keys());console.log(set.values());console.log(set.entries());/*** SetIterator {1, 2, 3}* [[Entries]]* 0: 1* 1: 2* 2: 3* __proto__: Set Iterator* [[IteratorHasMore]]: true* [[IteratorIndex]]: 0* [[IteratorKind]]: "values"*//*** SetIterator {1, 2, 3}* [[Entries]]* 0: 1* 1: 2* 2: 3* __proto__: Set Iterator* [[IteratorHasMore]]: true* [[IteratorIndex]]: 0* [[IteratorKind]]: "values"*//*** SetIterator {1 => 1, 2 => 2, 3 => 3}* [[Entries]]* 0: {1 => 1}* 1: {2 => 2}* 2: {3 => 3}* __proto__: Set Iterator* [[IteratorHasMore]]: true* [[IteratorIndex]]: 0* [[IteratorKind]]: "entries"*/
for of循环迭代器对象
let set = new Set([1, 2, 3]);//set结构没有键名//键名和键值是一致的for (let i of set.keys()) {console.log(i);//1 2 3}for (let i of set.values()) {console.log(i);//1 2 3}for (let i of set.entries()) {console.log(i);//[1, 1] [2, 2] [3, 3]}
单独遍历具有迭代器接口的set
for (let i of set) {console.log(i);//1 2 3}//验证是返回值从哪个接口返回出来的? set.keys()或set.values()console.log(Set.prototype[Symbol.iterator] === Set.prototype.values); //trueconsole.log(Set.prototype[Symbol.iterator] === Set.prototype.keys); //true//说明set依赖set.values()方法遍历出来的值
//forEach()遍历let set = new Set([1, 2, 3]);set.forEach(function (value, keys, arr) {console.log(value); //1 1 Set{1, 2, 3}console.log(keys); //2 2 Set{1, 2, 3}console.log(arr); //3 3 Set{1, 2, 3}});
set适用展开运算符
//利用展开运算符可以将set数据轻松转为数组结构let set = new Set([1, 2, 3]);console.log(...set); //1 2 3console.log([...set]); //[1, 2, 3]
//将set数据里的数值乘以2//利用数组map()方法映射let set = new Set([1, 2, 3]);console.log([...set].map(value => value * 2));//[2, 4, 6]//写法二:利用Array.from()console.log(Array.from(set, value => value * 2));
//set+filter过滤数组里的数值//返回偶数组合的数组let set = new Set([1, 2, 3, 4, 5, 6]);console.log([...set].filter(x => (x % 2) == 0));//[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}//交集 过滤掉b 有b里的值就通过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}
数据结构对比
//map & array 对比let map = new Map();let array = new Array();//增map.set('t', 1);array.push({'t': 1});console.log(map); //Map(1) {"t" => 1}console.log(array); //[{t: 1}]//查console.log(map.has('t')); //trueconsole.log(array.find(val => val['t'])); //{t: 1}//改map.set('t', 2)console.log(map);array.forEach(item => item.t ? item.t = 2 : '');console.log(array); //[{t: 2}]//删map.delete('t');console.log(map); //Map(0) {}let index = array.findIndex(item => item.t);array.splice(index, 1);console.log(array); //[]//总结:map要更方便些
//set & array 对比let set = new Set();let array = [];//把引用值存起来let obj = {t: 1};//增set.add(obj);array.push({'t': 1});console.log(set); //{[{t: 1}]}console.log(array); //[{t: 1}]//查console.log(set.has(obj)); //trueconsole.log(array.find(val => val['t'])); //{t: 1}//改set.forEach(item => item.t ? item.t = 2 : '');console.log(set); //{[{t: 2}]}array.forEach(item => item.t ? item.t = 2 : '');console.log(array); //[{t: 2}]//删set.forEach(item => item.t ? set.delete(item) : '');console.log(set); //{}let index = array.findIndex(item => item.t);array.splice(index, 1);console.log(array); //[]//总结:操作方式set比array稍微略简
//map set object 横向对比let item = {t: 1};let map = new Map();let set = new Set();let obj = {};//增map.set('t', 1);set.add(item);obj['t'] = 1;console.log(map, set, obj);//{"t" => 1}//{{0:{t: 1}}}//{t: 1}//查console.log(map.has('t')); //trueconsole.log(set.has(item)); //trueconsole.log('t' in obj); //true//改map.set('t', 2);set.forEach(item => item.t ? item.t = 2 : '');obj['t'] = 2;console.log(map, set, obj);//{"t" => 2}//{{0:{t: 2}}}//{t: 2}//删map.delete('t');set.delete(item);delete obj['t'];console.log(map, set, obj);//{} {} {}//总结://1.map,set底层优化比obj,arr要好些//2.map,set操作方式比obj,arr更优雅//建议://1.优先使用map//2.对数据有唯一性要求的用set
WeakMap&WeakSet
阉割版的Map和Set
具有方法:add()/delete()/has()
没有遍历方法
垃圾回收机制不考虑他们俩的引用
WeekMap和Map有什么区别?
Map的键名可以是任意类型- 普通对象的键名只能是字符串
WeekMap的键名只能是对象
注:
WeekMap的键名是弱引用对象,只要引用没有了,关联的键值也会一并被垃圾回收
应用场景:
- 对于一些属性或方法数据用完时或绑定事件处理函数想销毁时,不需要手动销毁,利用
WeekMap就可以处理销毁 - 在深拷贝中,利用
WeekMap可以实现记录已经被拷贝过的对象或数组,来避免重复拷贝导致内存泄漏,浏览器死循环报错等问题
//WeakSet&WeakMap成员只能是对象不能为别的数据,否则报错console.log(new WeakSet().add(1));
