浅拷贝:当对对象进行赋值操作时,赋值后的新对象,如果改变其中第一层级的值,那么 赋值后的 新对象 相应的值也 会 随之改变,这就是对象的浅层次的拷贝,也称为 浅拷贝
深拷贝:当对对象进行赋值操作时,赋值后的新对象,如果改变其中第二层级或二层级后的其他层级的值,那么 赋值后的 新对象 相应的值也 不会 随之改变,这就是对象的深层次的拷贝,也称为 深拷贝
案例如下:
let obj = {
a: 100,
b: 200
}
let newObj = obj
obj.a = 100
console.log(obj) // { a: 200, b: 200 }
console.log(newObj) // { a: 200, b: 200 }
由以上案例可发现,赋值后的对象newObj 会随着 旧对象obj 的值的改变而改变
因为 obj 是引用数据类型,引用数据类型存在于 堆内存 中,而堆空间的内存会被 栈内存 中的引用地址进行一个联系,而 对象的拷贝 拷贝的其实是存在于 栈内存 中的引用地址,这就导致了 两个引用地址 指向同一 堆内存 空间,如果修改其中的一个内存引用地址,那么就会对同一堆空间的数据进行修改,这就导致了数据混乱,也就是浅拷贝带来的问题
解决这种的问题 的 方案有很多,我从 es 3 到 es6 的方案都整理下
定义一个空对象,通过 fon in 循环 把旧数据拷贝一份
let obj = { a:100, b:200 }
function es3Copy(obj){
let newObj = {}
for (const key in obj) {
newObj[key] = obj[key]
}
return newObj
}
let es3 = es3Copy(obj)
obj.a = 101
console.log(obj) // { a:101, b:200 }
console.log(es3) // { a:100, b:200 }
通过 Object.defineProperty() 的方式
let obj = { a:100, b:200 }
function es5Copy(obj){
let newObj = {}
Object.getOwnPropertyNames(obj).forEach( v => {
let des = Object.getOwnPropertyDescriptor(obj, v)
Object.defineProperty(newObj, v, des)
})
return newObj
}
let es5 = es3Copy(obj)
obj.a = 101
console.log(obj) // { a:101, b:200 }
console.log(es5) // { a:100, b:200 }
let obj = { a:100, b:200 }
function es6Copy(obj){
let newObj = {}
// console.log(Object.keys(obj));
// console.log(Object.values(obj));
// console.log(Object.entries(obj))
for (const [key, value] of Object.entries(obj)) {
newObj[key] = value
}
return newObj
}
let es6 = es6Copy(obj)
obj.a = 101
console.log(obj) // { a:101, b:200 }
console.log(es6) // { a:100, b:200 }
这句话待严谨:javaScript存储对象都是存地址的,所以浅拷贝会导致 obj1 和obj2 指向同一块内存地址。改变了其中一方的内容,都是在原来的内存上做修改会导致拷贝对象和源对象都发生改变,而深拷贝是开辟一块新的内存地址,将原对象的各个属性逐个复制进去。对拷贝对象和源对象各自的操作互不影响。
运用递归,一层一层的拷贝下去
let obj = {
a: 1,
b: 2,
c: {
d: {
e: 100
}
}
}
function deepCopy(obj){
let newObj = Array.isArray(obj) ? [] : {}
if( obj && typeof newObj ==='object' ){
for(const key in obj){
if( typeof obj[key] === 'object' ){
newObj[key] = deepCopy(obj[key])
}else{
newObj[key] = obj[key]
}
}
}else{
return '请传入正确的数据类型'
}
return newObj
}
let res = deepCopy(obj)
obj.c.d.e = 200
console.log(obj)
console.log(res)
JSON.parse(JSON.stringify(obj)) 序列化 和 反序列化
let obj = {
a: 1,
b: 2,
c: {
d: {
e: 100
}
}
}
let newObj = JSON.parse(JSON.stringify(obj))
obj.c.d.e = 200
console.log(obj)
console.log(newObj)
我们在使用 JSON.parse(JSON.stringify(xxx))时应该注意一下几点:
JSON.stringify
后再JSON.parse
的结果,时间将只是字符串的形式。而不是时间对象var test = {
name: 'a',
date: [new Date(1536627600000), new Date(1540047600000)],
};
let b;
b = JSON.parse(JSON.stringify(test))
RegExp
、Error
对象,则序列化的结果将只得到空对象;const test = {
name: 'a',
date: new RegExp('\\w+'),
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'
console.error('ddd', test, copyed)
undefined
,则序列化的结果会把函数或 undefined丢失;const test = {
name: 'a',
date: function hehe() {
console.log('fff')
},
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'
console.error('ddd', test, copyed)
JSON.stringify()
只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))
深拷贝后,会丢弃对象的constructor
;function Person(name) {
this.name = name;
console.log(name)
}
const liai = new Person('liai');
const test = {
name: 'a',
date: liai,
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'
console.error('ddd', test, copyed)