在说浅拷贝深拷贝之前,我们在说下赋值
赋值
赋值是将某一数值或某对象赋给某个变量的过程,分为下面两部分
基本数据类型:赋值之后两个变量互不影响
let a = 10;
let b = a;
a = 20;
console.log('a', a); //20
console.log('b', b); //10
引用数据类型:两个变量具有相同的引用,指向同一个对象,相互之间有影响
let a = {
name: '桀桀桀'
}
let b = a
a.name = '哈哈哈' // 对引用类型进行赋值操作,两个变量指向相同的对象,a里面的数据改变,b也会该改变
console.log('obj', a); //哈哈哈
console.log('src', b); //哈哈哈
2.浅拷贝和深拷贝的区别
浅拷贝和深拷贝都是对一份数据进行拷贝;
js分为基本数据类型和引用数据类,对于基本数据类型并没有深浅拷贝的区别,我们所有的深浅拷贝的区别只是针对引用类型。
浅拷贝和深拷贝都复制了值和地址,都是为了解决引用类型赋值后的互相影响的问题;
浅拷贝只进行了一层赋值 ,深层次的引用类型还是共享内存地址,源对象和拷贝对象还是会互相影响
深拷贝就是无限层级拷贝,深拷贝后源对象和拷贝不会互相影响;
3.浅拷贝
3.1、什么是浅拷贝
创建一个对象,把这个对象拷贝一下,如果属性是基本数据类型,拷贝的就是基本数据类型的值,如果属性是引用类型,拷贝的就是内存地址,所以就是如果其中一个对象进行改变了这个地址,就会影响到另一个对象。
![](https://i-blog.csdnimg.cn/blog_migrate/692afafc8511aab145a2c52d5c8e8c62.png)
如上图,浅拷贝只是拷贝了第一次的基本数据类型以及第一层的引用类型,浅拷贝可以说成只是赋值,两个对象的内存地址还是一样的。
3.2、浅拷贝的实现
let obj = {
name: '小明',
age: 2,
friend: {
name: '小刚'
}
}
// 浅拷贝
function copy(obj) {
let newObj = {}
for (let i in obj) {
newObj[i] = obj[i]
}
return newObj
}
let newObject = copy(obj)
obj.name = '小红'; //改变原来对象
console.log('原来对象', obj); //小红
console.log('新的对象', newObject) //小明
console.log('两者指向同一地址', obj == newObject);//false
浅拷贝只是解决了一层的数据处理,更深层的对象还是会指向同一地址,互相影响;
3.3、浅拷贝的使用
数组的slice和concat方法
// slice 方法返回一个新的数组对象
let arr = ['cat', 'dog', 'big']
let newArr = arr.slice(0);
arr[2] = 'tiger'; //改变原数组
console.log('原数组', arr); //'cat', 'dog', 'tiger'
console.log('新数组', newArr); //'cat', 'dog', 'big'
console.log('两者指向同一地址', arr == newArr); //false
//concat方法用于连接两个或多个数组,该方法不会改变现有的数组,而是返回一个新的数组。
let arr = ['cat', 'dog', 'big']
let newArr = [].concat(arr)
arr[2] = 'tiger'; //改变原数组
console.log('原数组', arr); //'cat', 'dog', 'tiger'
console.log('新数组', newArr); //'cat', 'dog', 'big'
console.log('两者指向同一地址', arr == newArr); //false
扩展运算符
let arr = ['cat', 'dog', 'big']
let newArr = [...arr]
arr[2] = 'tiger'; //改变原数组
console.log('原数组', arr); //'cat', 'dog', 'tiger'
console.log('新数组', newArr); //'cat', 'dog', 'big'
console.log('两者指向同一地址', arr == newArr); //false
数组静态语法Array.from
let arr = ['cat', 'dog', 'big']
let newArr = Array.from(arr)
arr[2] = 'tiger'; //改变原数组
console.log('原数组', arr); //'cat', 'dog', 'tiger'
console.log('新数组', newArr); //'cat', 'dog', 'big'
console.log('两者指向同一地址', arr == newArr); //false
Object.assign()
let obj = {
name: '小朱'
}
let newObj = Object.assign({}, obj)
obj.name = '小马'; //改变原来的对象
console.log('原数组', obj); //小马
console.log('新数组', newObj); //小朱
console.log('两者指向同一地址', obj == newObj); //false
4.深拷贝
4.1什么是深拷贝
深拷贝会拷贝所有的属性,在内存中开辟一个新的存储空间,完完整整的拷贝一个新的对象(数组),和原对象(数组)完全分隔,彼此之间的操作互相不影响。
![](https://i-blog.csdnimg.cn/blog_migrate/0b7c44282b7e1158623d920d9f76fb88.png)
4.2深拷贝的实现
下面的例子我使用了递归来实现了一个深拷贝,让原对象和克隆对象互不影响
let obj = {
name: '小明',
age: 2,
friend: {
name: '小刚'
}
}
function copy(obj) {
let newObj = {};
for (let i in obj) {
if (obj[i] instanceof Object) { //如果是引用类型,递归实现每层的拷贝
newObj[i] = copy(obj[i]) //递归拷贝每一次
} else {
newObj[i] = obj[i] //否则就无需拷贝
}
}
return newObj
}
let newObject = copy(obj);
obj.friend.name = '小李';
console.log('原对象', obj); // obj.friend.name是小李
console.log('新对象', newObject) // obj.friend.name小刚
console.log('两者指向同一地址', obj.friend == newObject.friend); //false
4.3深拷贝的使用
处理数组、日期、正则、null
上文中只是用了一个简单的列子讲解了深拷贝,但没有处理null这种原始类型,也没处理数组、日期和正则这种比较常用的引用类型。
let obj = {
arr: ['cat', 'dog', 'big'],
time: new Date(),
regular: /abc/,
type: null,
}
function copy(obj) {
// console.log(obj);
if (obj === null) return obj //处理null
if (obj instanceof Date) return new Date(obj) // 处理日期
if (obj instanceof RegExp) return new RegExp(obj) // 处理正则
let newObj = {};
for (let i in obj) {
if (obj[i] instanceof Object) { //如果是引用类型,递归实现每层的拷贝
newObj[i] = copy(obj[i]) //这个地方我使用了递归方法
} else {
newObj[i] = obj[i]
}
}
return newObj
}
let newObject = copy(obj);
console.log('原对象', obj); //
console.log('新对象', newObject) //
![](https://i-blog.csdnimg.cn/blog_migrate/b66771f990b5ff317c9e4843e66b9cb2.png)
上面的代码也可以这样实现,如下:
使用new 实例.constructor()
let obj = {
arr: ['cat', 'dog', 'big'],
time: new Date(),
regular: /abc/,
type: null,
}
function copy(obj) {
// console.log(obj);
if (obj === null) return obj //处理null
if (obj instanceof Date) return new Date(obj) // 处理日期
if (obj instanceof RegExp) return new RegExp(obj) // 处理正则
if (typeof obj !== 'object') return obj // 处理原始类型
let newObj = new obj.constructor(); //new实例
for (let i in obj) {
newObj[i] = copy(obj[i])
// if (obj[i] instanceof Object) { //如果是引用类型,递归实现每层的拷贝
// newObj[i] = copy(obj[i]) //这个地方我使用了递归方法
// } else {
// newObj[i] = obj[i]
// }
}
return newObj
}
let newObject = copy(obj);
console.log('原对象', obj); //
console.log('新对象', newObject) //
实例的 constructor 其实就是构造函数
class Person {}
let obj = new Person();
console.log(obj.constructor === Person); //true
console.log([].constructor === Array); //true
console.log({}.constructor === Object); //true
// ----------------------------------------
console.log(new [].constructor()); //[]
// ||
console.log(new Array()); //[]
// -----------------------------------------
console.log(new {}.constructor()) // {}
// ||
console.log(new Object()) // {}
使用 constructor就不用在拷贝时去判断数组类型了,原对象是对象时,就拷贝一个新对象,原对象是数组时,就拷贝一个新数组;
5.总结
关于浅拷贝和深拷贝的使用选择,保险的做法是所有的拷贝都用深拷贝,如果JSON.parse(JSON.stringify(object))可以实现的话,就可以直接JSON.parse(JSON.stringify(object)),还方便。