这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战
JavaScript 中,怎么拷贝对象呢?ES5 中的做法是把对象遍历一下,把数据逐项拷贝到目标对象中去;ES6 则提供了一个新的 API,实现对象数据的拷贝:
const target = {} // 目标对象(就是我要把数据拷贝到这个对象上来)
const source = { b: 4, c: 5 } // 源对象(就是要把数据拷贝出来的地方)
Object.assign(target, source) // 拷贝 source 对象的数据到 target 对象上来
console.log(target) // {b: 4, c: 5}
console.log(source) // {b: 4, c: 5}
但是,这个 API 是有“缺陷”的,比如目标对象和源对象的数据结构如下时,拷贝的结果就会丢失掉“h: 10”:
const target = {
a: {
b: {
c: {
d: 9
}
},
e: 5,
f: 6,
h: 10
},
i: 3
};
const source = {
a: {
b: {
c: {
d: 1
}
},
e: 2,
f: 3
}
};
Object.assign(target, source);
console.log(target);
/* 运行结果:
{
a: {
b: {
c: {
d: 1
}
},
e: 2,
f: 3
},
i: 3
}
*/
这样的结果是不合理的,拷贝可以修改原来的数据,但不应该删除原有的东西(原来的“h: 10”没有了)
实际上,Object.assign() 实现的是浅复制(对于不是引用类型的值,会进行数据的替换,
对于引用类型的值,则不再遍历,只是将引用的对象的地址进行了替换),而不是深复制,
所以才会出现上面的问题(a 是个对象,属于引用类型的值,所以在拷贝时拷贝的是地址值,
也就是用源对象中 a 的地址值替换掉了目标对象中 a 的地址值,a 里面的数据也就完全被替换掉了)
所以呢,使用 Object.assign(target, source) 时,source 到 target 的拷贝过程中,
可能会出现数据丢失的情况,就是它不能实现深拷贝,只能实现浅拷贝。
如果在使用 Object.assign() 时想要实现深拷贝,则还需要进行递归。
如果目标对象传入的是 undefined 和 null 将会怎么样呢?
const tar1 = undefined
const tar2 = null
const sou = {
a: 2,
b: 3,
c: 4
}
Object.assign(tar1, sou) // Uncaught TypeError: Cannot convert undefined or null to object
Object.assign(tar2, sou) // Uncaught TypeError: Cannot convert undefined or null to object
可见,目标对象传入的是 undefined 或 null 时,会报错,“不能将 undefined 或 null 转换为对象”。
如果源对象传入的是 undefined 和 null 将会怎么样呢?
let tar = {
a: {
b: {
c: {
d: 9
}
},
e: 5,
f: 6,
h: 10
},
i: 3
}
const sou1 = undefined
const sou2 = null
Object.assign(tar, sou1)
console.log(tar)
/* 运行结果:
{
a: {
b: {
c: {
d: 9
}
},
e: 5,
f: 6,
h: 10
},
i: 3
}
*/
tar = {
a: {
b: {
c: {
d: 9
}
},
e: 5,
f: 6,
h: 10
},
i: 3
}
Object.assign(tar, sou2)
console.log(tar)
/* 运行结果:
{
a: {
b: {
c: {
d: 9
}
},
e: 5,
f: 6,
h: 10
},
i: 3
}
*/
可见,源对象传入的是 undefined 或 null 时,拷贝后目标对象还是其原来的值。
如果目标对象是个嵌套的对象,子对象的属性会被覆盖吗?
let tg = {
a: {
b: 2,
d: 4
}
}
const sr1 = {
e: 7
}
Object.assign(tg, sr1)
console.log(tg)
/* 运行结果:
{
a: {
b: 2,
d: 4
},
e: 7
}
*/
tg = {
a: {
b: 2,
d: 4
}
}
const sr2 = {
a: {
b: 2
}
}
Object.assign(tg, sr2)
console.log(tg)
/* 运行结果:
{
a: {
b: 2
}
}
*/
可见,目标对象是个嵌套的对象时,如果源对象中没有目标对象的子对象名,源对象的数据会拷贝到子对象的后面,子对象的属性不会被覆盖;如果源对象中存在目标对象的子对象名时,子对象的属性会被覆盖。
如果目标对象中存在嵌套的对象,子对象的属性会被覆盖吗?
let tg = {
a: {
b: 2,
d: 4
},
c: 3
} // 目标对象中嵌套了子对象 a
const sr1 = {
e: 7
}
Object.assign(tg, sr1)
console.log(tg)
/* 运行结果:
{
a: {
b: 2,
d: 4
},
c: 3,
e: 7
}
*/
tg = {
a: {
b: 2,
d: 4
},
c: 3
} // 目标对象中嵌套了子对象 a
const sr2 = {
a: {
b: 5
},
c: 6,
e: 7
}
Object.assign(tg, sr2)
console.log(tg)
/* 运行结果:
{
a: {
b: 5
},
c: 6,
e: 7
}
*/
可见,目标对象中存在嵌套的对象时,如果源对象中没有目标对象的子对象名,源对象的数据会添加到目标对象中,子对象的属性不会被覆盖;如果源对象中存在目标对象的子对象名时,子对象的属性会被覆盖。