Глубокое клонирование объекта с использованием Object.fromEntries () - PullRequest
3 голосов
/ 16 апреля 2019

Некоторое время назад я читал о предложении нового метода Object.fromEntries () , который поддерживается в более новых версиях некоторых браузеров ( ссылка ). Читая об этом, я имел в виду идею использования этого метода для deep-clone и object вместо использования JSON.parse(JSON.stringify(obj)). До сих пор я пришел к следующему рекурсивному подходу, который, с моей точки зрения, сработал.

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;}

Ответы [ 3 ]

1 голос
/ 16 апреля 2019
  1. Глубокое клонирование прекрасно, поскольку глубоко вложенные объекты все еще сохраняют свои свойства - однако, массивы также необходимо клонировать; заменил obj.slice() на obj.map(o => cloneObj(o)).
  2. Этот метод на самом деле быстрее , чем JSON.parse(JSON.stringify(obj)) - три теста на JSBench и JSON были медленнее более чем на 10% каждый раз.
1 голос
/ 16 апреля 2019

Как уже упоминалось, ваши массивы не глубоко клонированы - используйте obj.map(cloneObj) вместо obj.slice().

Но другой оплошностью является typeof obj !== "object", который не работает для null. Лучше использовать Object(obj) !== obj.

1 голос
/ 16 апреля 2019

Вам не хватает одной вещи:

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;}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...