赋值
赋值是将某一数值或对象赋给某个变量的过程,分为下面2部分
- 基本数据类型:赋值,赋值之后两个变量互不影响
- 引用数据类型:赋址,两个变量具有相同的引用,指向同一对象,相互之间有影响
对基本类型进行赋值操作,两个变量互不影响。
let a = 'lili';
let b = a;
console.log(b); // lili
a = 'change';
console.log(a); // change
console.log(b); // lili
对引用类型进行赋址操作,两个对象指向同一对象,改变变量a之后会影响变量b,哪怕改变的只是对象a中的基本类型数据。
let a = {
name:'lili',
book: {
title:'You Don't Know JS',
price:'55'
}
}
let b = a;
console.log(b);
// {
// name:'lili',
// book:{title:'You Don't Know JS',price:'55'}
// }
a.name = 'change';
a.book.price = '65';
console.log(a);
// {
// name:'change',
// book:{title:'You Don't Know JS',price:'65'}
// }
console.log(b);
// {
// name:'change',
// book:{title:'You Don't Know JS',price:'65'}
// }
通常在开发中并不希望改变变量a之后会影响到变量b,这时就需要用到浅拷贝和深拷贝。
浅拷贝(Shallow Copy)
1. 什么是浅拷贝
创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
上图中,SourceObject是原对象,其中包含基本类型属性field1和引用类型属性refObj。浅拷贝之后基本类型数据field2和filed1是不同属性,互不影响。但引用类型refObj仍然是同一个,改变之后会对另一个对象产生影响。
简单来说可以理解为浅拷贝只解决了第一层的问题,拷贝第一层的基本类型值,以及第一层的引用类型地址
2. 浅拷贝使用场景
- Object.assign()
Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
有些文章说Object.assign()是深拷贝,其实是不正确的。(具体可查看MDN Web)
let a = {
name:'lili',
book:{
title:'You Don't Know JS',
price:'55'
}
}
let b = Object.assign({},a);
console.log(b);
//{
// name:'lili',
// book:{title:'You Don't Know JS',price:'55'}
//}
a.name = 'change';
a.book.price = '65';
console.log(a);
//{
// name:'change',
// book:{title:'You Don't Know JS',price:'65'}
//}
console.log(b);
//{
// name:'lili',
// book:{title:'You Don't Know JS',price:'65'}
//}
上面代码改变对象a之后,对象b的基本属性保持不变。但是当改变对象a中的对象book时,对象b相应的位置也发生了改变。
- 展开语法 Spread
let a = {
name:'lili',
book:{
title:'You Don't Know JS',
price:'55'
}
}
let b = {...a};
console.log(b);
//{
// name:'lili',
// book:{title:'You Don't Know JS',price:'55'}
//}
a.name = 'change';
a.book.price = '65';
console.log(a);
//{
// name:'change',
// book:{title:'You Don't Know JS',price:'65'}
//}
console.log(b);
//{
// name:'lili',
// book:{title:'You Don't Know JS',price:'65'}
//}
通过上面的代码可以看出实际效果和Object.assign()是一样的。
- Array.prototype.slice() slice()方法返回一个新的数组对象,这一对象是一个由begin和end(不包括end)决定的元数组的浅拷贝。原始数组不会被改变。
let a = [0,'1',[2,3]];
let b = a.slice(1);
console.log(b); // ['1',[2,3]]
a[1] = '99';
a[2][0] = 4
console.log(a); // [0,99,[4,3]]
console.log(b); // ['1',[4,3]]
可以看出,改变a[1]之后b[0]的值并没有发生变化,但改变a[2][0]之后,相应的b[1][0]的值也发生变化。说明slice()方法是浅拷贝,相应的还有concat等,在工作中面对复杂数组结构要额外注意。
深拷贝(Deep Copy)
1. 什么是深拷贝
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。
2. 深拷贝使用场景
JSON.parse(JSON.stringify(object))
let a = {
name:'lili',
book:{
title:'You Don't Know JS',
price:'55'
}
}
let b = JSON.parse(JSON.stringify(a));
console.log(b);
//{
// name:'lili',
// book:{title:'You Don't Know JS',price:'55'}
//}
a.name = 'change';
a.book.price = '65';
console.log(a);
//{
// name:'change',
// book:{title:'You Don't Know JS',price:'65'}
//}
console.log(b);
//{
// name:'lili',
// book:{title:'You Don't Know JS',price:'55'}
//}
完全改变变量a之后对变量b没有任何影响,这就是深拷贝的魔力。
我们看下对数组的深拷贝效果如何
let a = [0,'1',[2,3]];
let b = JSON.parse(JSON.stringify(a.slice(1) ));
console.log(b); // ['1',[2,3]]
a[1] = '99';
a[2][0] = 4;
console.log(a); // [0,'99',[4,3]]
console.log(b); // ['1',[2,3]]
对数组深拷贝之后,改变原数组不会影响拷贝之后的数组。
但是该方法有以下几个问题:
- 会忽略undefined
- 会忽略symbol
- 不能序列号函数
- 不能解决循环引用的对象
- 不能正确处理 new Date()
- 不能处理正则
- undefined、symbol和函数这三种情况,会直接忽略。
let obj = {
name:'lili',
a:undefined,
b:Symbol('lili'),
c:function() {}
}
console.log(obj);
//{
// name:'lili',
// a:undefined,
// b:Symbol('lili'),
// c:f()
//}
let b = JSON.parse(JSON.stringify(obj));
console.log(b); // {name:'lili'}
- 循环引用情况下,会报错。
let obj = {
a:1,
b:{
c:2,
d:3
}
}
obj.a = obj.b;
obj.b.c = obj.a
let b = JSON.parse(JSON.stringify(obj));
// Uncaught TypeError: Converting circular structure to JSON
- new Date()情况下,转换结果不正确。
new Date();
// Tue May 24 2022 17:00:50 GMT+0800 (中国标准时间)
JSON.stringify(new Date());
// '"2022-05-24T09:02:35.223Z"'
JSON.parse(JSON.stringify(new Date() ));
// '2022-05-24T09:03:56.732Z'
解决方法:转成字符串或者时间戳就好了。
let date = (new Date()).valueOf();
// 1653383180011
JSON.parse(JSON.stringify(date));
// 1653383261334
- 正则情况下
let obj = {
name:'lili',
a:/'123'/
}
console.log(obj);
// {name:'lili',a:/'123'/}
let b = JSON.parse(JSON.stringify(obj));
console.log(b);
// {name:'lili',a:{}}
PS:为什么会存在这些问题可以学习一下JSON。 除了上面介绍的深拷贝方法,常用的还有jQuery.extend()和lodash.cloneDeep()。