什么是深浅拷贝?如何实现深浅拷贝
在聊深浅拷贝之前,先聊一下js数据类型,可以分为值类型(基本类型) 和 引用数据类型(对象类型) JS中的变量都是保存到栈内存中的 基本数据类型 的值直接在栈内存中存储,值与值之间是独立存在,引用数据类型 是保存到堆内存中的,每创建一个新的对象,就会在堆内存中开辟出一个新的空间,而栈里面保存的是对象的内存地址(对象的引用)
例如下面代码,在内存中大概存这样
let a = 2
let c = 'hello'
let obj1 = {
name:'obj'
}
let obj2 = obj
赋值和深浅拷贝的区别 之前,我一直认为赋值就是浅拷贝,现在发现是错的,赋值对于基本类型来说可以认为是浅拷贝,(基本类型没有深浅拷贝)。
浅拷贝 浅拷贝是指创建一个新的对象,把这个对象的原始属性精确拷贝一份,如果是基本类型就拷贝基本类似的值,如果是引用类型,拷贝的就是内存地址,如果其中一个引用类型改变了值,那么就会影响另一个对象
深拷贝 深拷贝是将一个对象从内存中完成拷贝出来,在内存中开辟一个新的区域存放对象,并且两者不会相互影响
赋值方法
const obj1 = {
name: "www",
age: 18
};
let obj2 = obj1;
obj2.age = 20;
console.log(obj1, obj2);
// 可以看到将obj1 赋值 给obj2 修改obj2的值obj1也会变
// { name: 'www' age: 18 } { name:'www' age: 20, }
浅拷贝方法
- Object.assign() 对象浅拷贝
const obj1 = {
name: "www",
age: 18
};
let obj2 = Object.assign({}, obj1);
obj2.age = 20;
console.log(obj1, obj2);
// 可以看到浅拷贝修改之后age的变更并不会相互影响
//{ name: 'www' age: 18 } { name:'www' age: 20, }
看到这里是不是觉得修改新对象的值旧对象不会改变还算浅拷贝吗?如果你也有这样的疑问,那么请往下看
const obj1 = {
name: "www",
age: 18,
hobby: {
ball: 'basketball',
track: 'running'
}
};
let obj2 = Object.assign({}, obj1);
obj2.age = 20;
obj2.hobby.ball = 'volleyball'
console.log(obj1, obj2);
// { name: 'www', age: 18, hobby: { ball: 'volleyball', track: 'running' } }
// { name: 'www', age: 20, hobby: { ball: 'volleyball', track: 'running' } }
这发现里修改了新对象hobby对象里面的ball值旧对象也会跟着改
其实Object.assign() 拷贝 当对象中只有一级属性,没有二级属性的时候,此方法为深拷贝,但是对象中有对象的时候,此方法,在二级属性以后就是浅拷贝。
- ... 展开运算符 解构浅拷贝
const obj1 = {
name: "www",
age: 18,
hobby: {
ball: 'basketball',
track: 'running'
}
};
let obj2 = { ...obj1 };
obj2.age = 20;
obj2.hobby.ball = 'volleyball'
console.log(obj1, obj2);
// { name: 'www', age: 18, hobby: { ball: 'volleyball', track: 'running' } }
// { name: 'www', age: 20, hobby: { ball: 'volleyball', track: 'running' } }
该方法和 Object.assign() 效果差不多
- 手动实现一个浅拷贝
function clone(source) {
if (typeof source === "object") {
let target = Array.isArray(source) ? [] : {};
for (let i in source) {
if (source.hasOwnProperty(i)) {
// source.hasOwnProperty(i)意思是_proto_上面的属性我们不拷贝
target[i] = source[i];
}
}
return target;
} else {
return target;
}
}
const obj1 = {
name: "www",
age: 18,
hobby: {
ball: 'basketball',
track: 'running'
}
};
let obj2 = clone(obj1);
obj2.age = 20;
obj2.hobby.ball = 'volleyball'
console.log(obj1, obj2);
// { name: 'www', age: 18, hobby: { ball: 'volleyball', track: 'running' } }
// { name: 'www', age: 20, hobby: { ball: 'volleyball', track: 'running' } }
深拷贝方法
- 简单的方法 JSON.parse(JSON.stringify())
只适用于固定的数据结构
这种方法虽然可以实现数组或对象深拷贝,但是有几种情况不能正确进行深拷贝
- obj里面有new Date(),深拷贝后,时间会变成字符串形式,而不是时间对象
- obj⾥有RegExp、Error对象,则序列化的结果会变成空对象{}
- obj⾥有function,undefined,则序列化的结果会把function或 undefined丢失
- obj里有NaN、Infinity、-Infinity,则序列化的结果会变成null
- JSON.string()只能序列化对象的可枚举的自有属性,如果obj中的对象是由构造函数生成的实例对象,深拷贝后会丢失对象的constructor
- lodash 库的_.cloneDeep
let _ = require("lodash");
let obj1 = {
a: 1,
b: { d: { e: 1, f: '123' } },
c: [1, 2, 3],
};
let obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f); // false
- 手动实现一个简单深拷贝
function deepClone(obj) {
//定义一个函数判断参数是否是对象
function isObject(o) {
return (typeof o == 'object' || typeof o == 'function') && o !== null
}
if (!isObject(obj)) { //如果不是对象
// throw new Error('非对象')
}
let isArray = Array.isArray(obj)
let newObj = isArray ? [...obj] : { ...obj }
//遍历newObj 此时如果newObj是对象key为对象的key 如果是数字 key 为数组的下标
Object.keys(newObj).forEach(key => {
newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
})
return newObj
}
let obj = {
a: [1, 2, 3],
b: {
c: 2,
d: 3
}
}
let newObj = deepClone(obj)
newObj.b.c = 1
console.log(obj); //{ a: [ 1, 2, 3 ], b: { c: 2, d: 3 } }
console.log(newObj); //{ a: [ 1, 2, 3 ], b: { c: 1, d: 3 } }