使用Object.fromEntries()深度克隆对象 [英] Deep-Cloning an object using Object.fromEntries()
问题描述
有时候,我读到有关新方法的建议 Object.fromEntries()在某些浏览器的较新版本中受支持(
const obj = {
key1: {key11: "key11", key12: "key12", key13: {key131: 22}},
key2: {key21: "key21", key22: "key22"},
key3: "key3",
key4: [1,2,3,4]
}
const cloneObj = (obj) =>
{
if (typeof obj !== "object")
return obj;
else if (Array.isArray(obj))
return obj.slice();
return Object.fromEntries(Object.entries(obj).map(
([k,v]) => ([k, cloneObj(v)])
));
}
// Clone the original object.
let newObj = cloneObj(obj);
// Make changes on the original object.
obj.key1.key11 = "TEST";
obj.key3 = "TEST";
obj.key1.key13.key131 = "TEST";
obj.key4[1] = "TEST";
// Display both objects on the console.
console.log("Original object: ", obj);
console.log("Cloned object: ", newObj);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
但是,我不确定是否要完成某个对象的deep-clone
某件事.所以我的问题是:
1)我缺少一些重要的东西来完成对象的deep-clone
?
2)在这种方法还可以的情况下,您是否认为此方法比使用JSON.parse()
和JSON.stringify()
更好?
提前谢谢!
更新1
这是更新版本,其中包含答案的反馈:
const obj = {
key1: {key11: "key11", key12: "key12", key13: {key131: 22}},
key2: {key21: "key21", key22: "key22"},
key3: "key3",
key4: [1,2,3,{key: "value"}]
}
const cloneObj = (obj) =>
{
if (Object(obj) !== obj)
return obj;
else if (Array.isArray(obj))
return obj.map(cloneObj);
return Object.fromEntries(Object.entries(obj).map(
([k,v]) => ([k, cloneObj(v)])
));
}
// Clone the original object.
let newObj = cloneObj(obj);
// Make changes on the original object.
obj.key1.key11 = "TEST";
obj.key3 = "TEST";
obj.key1.key13.key131 = "TEST";
obj.key4[1] = "TEST";
obj.key4[3].key = "TEST";
// Display both objects on the console.
console.log("Original object: ", obj);
console.log("Cloned object: ", newObj);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
更新2
针对第二个问题添加了效果比较:
Tested on Firefox 66.0.3 (64bits):
Test_JSON: 1923.000ms
Test_cloneObj: 2047.000ms
Tested on Chrome 73.0.3683.103 (64 bits):
Test_JSON: 2276.560ms
Test_cloneObj: 1903.675ms
const cloneObj = (obj) =>
{
if (Object(obj) !== obj)
return obj;
else if (Array.isArray(obj))
return obj.map(cloneObj);
return Object.fromEntries(Object.entries(obj).map(
([k,v]) => ([k, cloneObj(v)])
));
}
// Generate an object.
const getRandom = (min, max) => Math.floor(Math.random() * (max - min) + min);
let obj = {};
for (let i = 0; i < 100000; i++)
{
obj["Array" + i] = Array.from({length: 100}, () => getRandom(0, 1000));
obj["Obj" + i] = {"key": getRandom(0, 1000)};
obj["Const" + i] = "some_string";
}
// Test performance on JSON.parse()/stringify()
console.time("Test_JSON");
let obj1 = JSON.parse(JSON.stringify(obj));
console.timeEnd("Test_JSON");
// Test performance on cloneObj().
console.time("Test_cloneObj");
let obj2 = cloneObj(obj);
console.timeEnd("Test_cloneObj");
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
您缺少一件事:
else if (Array.isArray(obj))
return obj.slice();
这将返回数组的浅表副本.如果数组包含对象,则不会克隆这些基础对象:
const obj = [
['foo']
];
const cloneObj = (obj) =>
{
if (typeof obj !== "object")
return obj;
else if (Array.isArray(obj))
return obj.slice();
return Object.fromEntries(Object.entries(obj).map(
([k,v]) => ([k, cloneObj(v)])
));
}
// Clone the original object.
let newObj = cloneObj(obj);
// Make changes on the original object.
obj[0][0] = 'bar';
// Display both objects on the console.
console.log("Original object: ", obj);
console.log("Cloned object: ", newObj);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
要修复此问题,请改为return obj.map(cloneObj);
:
const obj = [
['foo']
];
const cloneObj = (obj) =>
{
if (typeof obj !== "object")
return obj;
else if (Array.isArray(obj))
return obj.map(cloneObj);
return Object.fromEntries(Object.entries(obj).map(
([k,v]) => ([k, cloneObj(v)])
));
}
// Clone the original object.
let newObj = cloneObj(obj);
// Make changes on the original object.
obj[0][0] = 'bar';
// Display both objects on the console.
console.log("Original object: ", obj);
console.log("Cloned object: ", newObj);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
Sometime ago, I read about the proposal of the new method Object.fromEntries() that is supported on newer versions of some browsers (reference). While reading about it, I had in mind the idea of use this method to deep-clone
an object
in replacement of using JSON.parse(JSON.stringify(obj))
. So far, I have come with the next recursive approach that from my point of view appears to work.
const obj = {
key1: {key11: "key11", key12: "key12", key13: {key131: 22}},
key2: {key21: "key21", key22: "key22"},
key3: "key3",
key4: [1,2,3,4]
}
const cloneObj = (obj) =>
{
if (typeof obj !== "object")
return obj;
else if (Array.isArray(obj))
return obj.slice();
return Object.fromEntries(Object.entries(obj).map(
([k,v]) => ([k, cloneObj(v)])
));
}
// Clone the original object.
let newObj = cloneObj(obj);
// Make changes on the original object.
obj.key1.key11 = "TEST";
obj.key3 = "TEST";
obj.key1.key13.key131 = "TEST";
obj.key4[1] = "TEST";
// Display both objects on the console.
console.log("Original object: ", obj);
console.log("Cloned object: ", newObj);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
However, I'm in doubt if I'm not missing something to accomplish the deep-clone
of an object. So my questions are:
1) I'm missing something important in order to accomplish the deep-clone
of an object?
2) In the case the approach is all right, do you believe this can performs better than using JSON.parse()
and JSON.stringify()
?
Thanks in advance!
Update 1
Here is the updated version with the feedback provided on the answers:
const obj = {
key1: {key11: "key11", key12: "key12", key13: {key131: 22}},
key2: {key21: "key21", key22: "key22"},
key3: "key3",
key4: [1,2,3,{key: "value"}]
}
const cloneObj = (obj) =>
{
if (Object(obj) !== obj)
return obj;
else if (Array.isArray(obj))
return obj.map(cloneObj);
return Object.fromEntries(Object.entries(obj).map(
([k,v]) => ([k, cloneObj(v)])
));
}
// Clone the original object.
let newObj = cloneObj(obj);
// Make changes on the original object.
obj.key1.key11 = "TEST";
obj.key3 = "TEST";
obj.key1.key13.key131 = "TEST";
obj.key4[1] = "TEST";
obj.key4[3].key = "TEST";
// Display both objects on the console.
console.log("Original object: ", obj);
console.log("Cloned object: ", newObj);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
Update 2
Added a performance comparison in relation to my second question:
Tested on Firefox 66.0.3 (64bits):
Test_JSON: 1923.000ms
Test_cloneObj: 2047.000ms
Tested on Chrome 73.0.3683.103 (64 bits):
Test_JSON: 2276.560ms
Test_cloneObj: 1903.675ms
const cloneObj = (obj) =>
{
if (Object(obj) !== obj)
return obj;
else if (Array.isArray(obj))
return obj.map(cloneObj);
return Object.fromEntries(Object.entries(obj).map(
([k,v]) => ([k, cloneObj(v)])
));
}
// Generate an object.
const getRandom = (min, max) => Math.floor(Math.random() * (max - min) + min);
let obj = {};
for (let i = 0; i < 100000; i++)
{
obj["Array" + i] = Array.from({length: 100}, () => getRandom(0, 1000));
obj["Obj" + i] = {"key": getRandom(0, 1000)};
obj["Const" + i] = "some_string";
}
// Test performance on JSON.parse()/stringify()
console.time("Test_JSON");
let obj1 = JSON.parse(JSON.stringify(obj));
console.timeEnd("Test_JSON");
// Test performance on cloneObj().
console.time("Test_cloneObj");
let obj2 = cloneObj(obj);
console.timeEnd("Test_cloneObj");
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
You are missing one thing:
else if (Array.isArray(obj))
return obj.slice();
This will return a shallow copy of the array. If the array contains objects, those underlying objects will not be cloned:
const obj = [
['foo']
];
const cloneObj = (obj) =>
{
if (typeof obj !== "object")
return obj;
else if (Array.isArray(obj))
return obj.slice();
return Object.fromEntries(Object.entries(obj).map(
([k,v]) => ([k, cloneObj(v)])
));
}
// Clone the original object.
let newObj = cloneObj(obj);
// Make changes on the original object.
obj[0][0] = 'bar';
// Display both objects on the console.
console.log("Original object: ", obj);
console.log("Cloned object: ", newObj);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
To fix it, return obj.map(cloneObj);
instead:
const obj = [
['foo']
];
const cloneObj = (obj) =>
{
if (typeof obj !== "object")
return obj;
else if (Array.isArray(obj))
return obj.map(cloneObj);
return Object.fromEntries(Object.entries(obj).map(
([k,v]) => ([k, cloneObj(v)])
));
}
// Clone the original object.
let newObj = cloneObj(obj);
// Make changes on the original object.
obj[0][0] = 'bar';
// Display both objects on the console.
console.log("Original object: ", obj);
console.log("Cloned object: ", newObj);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
这篇关于使用Object.fromEntries()深度克隆对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!