... 解构赋值、Set&Map

结构赋值

解构的方式都是根据key来实现的

1
2
3
4
5
6
7
8
9
// 数组
let arr = ['姓名','年龄'];
let [name, age] = ['姓名','年龄'];
console.log(name, age);
// 执行结果: 姓名 年龄

let [,age] = ['姓名','年龄'];
console.log(age);
// 执行结果: 年龄
1
2
3
4
// 对象
let {name,age} = {name: '加菲', age: 18};
console.log(name,age);
// 执行结果: 加菲 18

用:来重新命名 用=来赋值默认值

1
2
3
let {name,age:age1, addr="杭州"} = {name: '加菲', age: 18};
console.log(name,age1);
// 执行结果: 加菲 18

剩余运算符 只能用在最后一项

1
2
3
4
// 数组
let [name,...args] = ['加菲', 10, '杭州'];
console.log(args);
// 执行结果: [ 10, '杭州' ]
1
2
3
4
// 对象
let {name, ...args} = {name: '加菲', age: 18};
console.log(args);
// 执行结果: { age: 18 }

展开运算符

1
2
3
4
// 数组
let arr = [1,2,3];
console.log(...arr); //可以理解为把[]中间的内容拿出来
// 执行结果:1 2 3
1
2
3
4
5
// 对象
let arr2 = {name: '加菲', age: 18};
console.log(...arr2);
// 这样是不行的,相等于把大括号拿走 打印 console.log(name: '加菲', age: 18);
// 执行结果: Found non-callable @@iterator

展开运算符 可以合并数组 合并对象

1
2
3
4
5
// 数组
let a1 = [1,2,3];
let a2 = [4,5,6];
console.log([...a1,...a2]);
// 执行结果:[ 1, 2, 3, 4, 5, 6 ]
1
2
3
4
5
// 对象
let a1 = {a: 1};
let a2 = {b: 2};
console.log({...a1, ...a2});
// 执行结果: { a: 1, b: 2 }

Set & Map

set 、 map 是es6中新的数据类型 不能放重复项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let set = new Set([1,2,3,3]);
console.log(set);
// 执行结果: Set { 1, 2, 3 } 没有key属性

// 基本操作:
set.add(4);
set.clear();// 清除
set.delete(); // 清除某一项
set.entries();

// set 可以被迭代 Symbol.iterator set只有forEach
let set = new Set([1,2,3,3]);
set.forEach( item=>{
console.log(item); // 1 2 3
})
console.log(set.has(1)); // 有没有1 执行结果:true

示例

数组的去重 并集 交集 差集

并集

1
2
3
4
5
6
7
8
9
10
let a1 = [1,2,3,1,2,3];
let a2 = [4,5,6,1,2,3];
// 首先去重
let s = new Set([...a1, ...a2]);
console.log(s, typeof s); // 现在合并并且去重了,但是现在还是set
// 执行结果:Set { 1, 2, 3, 4, 5, 6 } 'object'

// 因为set里面也有Symbol.iterator 可以被迭代 所以可以直接打印
console.log([...s]);
// 执行结果: [ 1, 2, 3, 4, 5, 6 ]

交集

1
2
3
4
5
6
7
8
9
let a1 = [1,2,3,1,2,3];
let a2 = [4,5,6,1,2,3];
a1 = new Set([...a1]);
a2 = new Set([...a2]);
let s1 = [...a2].filter( item=>{ // filter是es5里的 返回的是一个新的数组
return a1.has(item);
})
console.log(s1);
// 执行结果: [ 1, 2, 3 ]

差集

你有的我没有

1
2
3
4
5
6
7
8
9
let a1 = [1,2,3,1,2,3];
let a2 = [4,5,6,1,2,3];
a1 = new Set([...a1]);
a2 = new Set([...a2]);
let s1 = [...a2].filter( item=>{ // 过滤
return !a1.has(item); // map是映射一个新的数组,但是不会比以前少,所以这里不能用map
})
console.log(s1);
// 执行结果:[ 4, 5, 6 ]

对象的结构赋值

1
2
3
4
5
let school = {name: '加菲', age: 18};
let addr = {addr: '杭州'};
let newObj = {...school, ...addr}; // 和数组一样 相当于把{}大括号里面的展开
console.log(newObj);
// 执行结果: { name: '加菲', age: 18, addr: '杭州' }

如果key 一样 会被后面的值覆盖

1
2
3
4
5
let school = {name: '加菲', age: 18};
let addr = {addr: '杭州', age: 19};
let newObj = {...school, ...addr};
console.log(newObj);
//执行结果:{ name: '加菲', age: 19, addr: '杭州' }
1
2
3
4
5
6
7
8
9
10
11
let school = {name: '加菲', age: 18, a: {b:1}};
let addr = {addr: '杭州', a: {b: 2}};
let newObj = {...school, ...addr};
console.log(newObj);
// { name: '加菲', age: 18, a: { b: 2 }, addr: '杭州' }
// 修改
newObj.a.b = 3;
console.log(newObj);
// { name: '加菲', age: 18, a: { b: 3 }, addr: '杭州' }
console.log(school,addr);
// { name: '加菲', age: 18, a: { b: 1 } } { addr: '杭州', a: { b: 3 } }

这里面 addr里面的值也改变了,因为引用的是同一个地址
现在这里是浅拷贝
如何实现深拷贝:

1
2
3
4
5
6
7
8
9
10
// JSON.parse JSON.stringify
let school = {name: '加菲', age: 18, a: {b:1}};
let addr = {addr: '杭州', a: {b: 2}};
let newObj = {...school, ...addr};
newObj = JSON.parse(JSON.stringify(newObj));
newObj.a.b = 3;
console.log(newObj);
// { name: '加菲', age: 18, a: { b: 3 }, addr: '杭州' }
console.log(school,addr);
// { name: '加菲', age: 18, a: { b: 1 } } { addr: '杭州', a: { b: 2 } }

可以实现 但是如下:

1
2
3
4
5
6
7
8
9
10
let school = {name: '加菲', age: 18, a: {b:1}, b: ()=>{}, c: undefined, reg: /\d+/};
let addr = {addr: '杭州', a: {b: 2}};
let newObj = {...school, ...addr};
newObj = JSON.parse(JSON.stringify(newObj));
newObj.a.b = 3;
console.log(newObj);
// { name: '加菲', age: 18, a: { b: 3 }, reg: {}, addr: '杭州' } b、c没有了,reg也不对
console.log(school,addr);
// { name: '加菲', age: 18, a: { b: 1 }, b: [Function: b], c: undefined, reg: /\d+/ }
// { addr: '杭州', a: { b: 2 } }

所以说 函数、undefined、正则不能完全拷贝

递归拷贝

首先要判断数据类型
instanceof 可以判断类型 判断是谁的实例
constructor 构造函数
在控制台打印 ({}).constructor     ([]).constructor 可以知道它的构造函数是谁 是什么类型的 Object()    Array()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const deepClone = (value) => {
if(value == null) return value; // 这里排除掉了null 和 undefined的情况
if(typeof value !== 'object') return value; // 这里面包含了函数类型 很少有拷贝函数的,这里先不判断了
if(value instanceof RegExp) return new RegExp(value); // 这里返回一个新的正则
if(value instanceof Date) return new Date(value);
// 拷贝的可能是对象或数组 (循环) 数组对象都能循环 for in
let instance = new value.constructor; //根据当前属性构造一个新的实例
//console.log(instance);
for(let key in value){
if(value.hasOwnProperty(key)){ // 拷贝自己的属性 不拷贝原型链上 过滤掉原型链上的属性
// 当value[key] 有多层
instance[key] = deepClone(value[key]);
}
}
return instance;
}
let a1 = [1,2,3,[4,5,6]];
let a2 = deepClone([1,2,3,[4,5,6]]);
console.log(a1,a2);
// [ 1, 2, 3, [ 4, 5, 6 ] ] [ 1, 2, 3, [ 4, 5, 6 ] ]
a2[3] = 'haha';
console.log(a1,a2);
// [ 1, 2, 3, [ 4, 5, 6 ] ] [ 1, 2, 3, 'haha' ]

这样的深拷贝还有问题 会有死循环的可能,如下

1
2
3
let obj = {b: 1};
obj.b = obj;
console.log(deepClone(obj)); // 这样就死循环了

解决 如果obj已经被拷贝一次了,下次在用到obj的时候 直接返回 不用再次拷贝
Map:

1
2
let newMap = new Map([[1,2], [3,4]]); // 存的是键值对的形式  也是不能放重复的项 用法和Set差不多
console.log(newMap); // Map { 1 => 2, 3 => 4 }

现在将key的值变为了一个对象 如下:

1
2
let newMap = new Map([[{a: 1},2], [3,4]]); 
console.log(newMap); //Map { { a: 1 } => 2, 3 => 4 }

但是这样会有一个问题 容易造成内存泄漏,如下:
新建一个html文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<html>
<head></head>
<body>
<script>
class My{
constructor(){
this.a = 1;
}
}
let obj = new My();
let newMap = new Map([[obj, 2], [3, 4]]); // 这里面key是obj
obj = null; // 现在我obj不要了 但是上面newMap里面还用着obj
// v8 垃圾回收 标记引用 现在obj被引用了2次 line10、line11 在line12的时候把obj的引用去掉了,还有一次,v8是不会把它回收掉的,因为它还在newMap中引用,当newMap销毁的时候才会把它回收
// 现在实现以下,用浏览器打开这个页面
// 打开F12 => Memory => 下面有个 Take snapshot拍快照的按钮 点击按钮 之后可以在搜索框里面搜索My 可以搜索到,意思表示这里我执行了line12销毁了,但是line11还在用所以没有销毁
// 现在把line11注释掉,再浏览器清空 再拍快照 可以发现My没有搜到 这里面搜My是因为比较好找,用对象会找的比较麻烦
// 现在我们可以换成WeakMap 如下:

class My{
constructor(){
this.a = 1;
}
}
let obj = new My();
let newMap = new WeakMap([[obj, 2]]); // 弱引用 key只能放对象
obj = null;
// 重新运行下 可以看到 My就没有被搜到了 这里line26 obj被销毁了,line25也被销毁了

</script>
</body>
</html>

下面就可以用WeakMap来记录已经被拷贝的值 防止重复拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const deepClone = (value, hash = new WeakMap()) => {
if(value == null) return value;
if(typeof value !== 'object') return value;
if(value instanceof RegExp) return new RegExp(value);
if(value instanceof Date) return new Date(value);
if(hash.has(value)) return hash.get(value); //先去hash中查看一下是否存在,有就把之前拷贝的值返回
let instance = new value.constructor;
hash.set(value, instance); // 没有就存储
for(let key in value){
if(value.hasOwnProperty(key)){
instance[key] = deepClone(value[key], hash); // 将hash继续向下传递
}
}
return instance;
}
let obj = {a: 1};
obj.b = obj;
console.log(deepClone(obj)); // { a: 1, b: [Circular] } b是一个引用类型 引用的是自己

tips:

null == undefined // true    null === undefined // false
for in 当遍历对象时   只遍历对象自身的和继承的可枚举的属性。 会把.length也当做键值遍历出来