Javascript-форум (https://javascript.ru/forum/)
-   Библиотеки/Тулкиты/Фреймворки (https://javascript.ru/forum/library-toolkit-framework/)
-   -   Создание иммутабельной копии объекта (https://javascript.ru/forum/library-toolkit-framework/78677-sozdanie-immutabelnojj-kopii-obekta.html)

Andrew K 18.10.2019 18:12

Создание иммутабельной копии объекта
 
Здравствуйте. Есть ли какие-нибудь библиотеки, которые делают иммутабельные копии объектов, но без указания пути вложенности подобъектов? Например есть переменная container хранящая объект с большой глубиной вложенных подобъектов. И есть другая переменная obj хранящая ссылку на объект вложенный в container (неизвестно на каком уровне вложенность). И нужно создать иммутабельную копию container чтобы изменить объект, на который указывает obj. При этом оригинальный объект был бы не затронут. То есть функция, которая должна это сделать (на рисунке называется makeImmutableCopy), должна найти в container расположение объекта, на который указывает obj, и сделать поверхностные копии каждого родительского объекта (на рисунке они имеют зелёную обводку).

В документации Реакта указываются библиотеки https://github.com/immerjs/immer и https://github.com/kolodny/immutability-helper. Но они требуют указывать полный путь, поэтому не подходят.

Andrew K 22.10.2019 18:44

Написал функцию делающую неизменяемую копию: https://github.com/AndrewKozinsky/ma...mutableCopy.js

Malleys 23.10.2019 13:29

Цитата:

Сообщение от Andrew K
Написал функцию делающую неизменяемую копию

Цитата:

// Перебрать все элементы массива
        for(let i = 0; i < newData.length; i++) {

// Если это объект
    if(mainData + '' === "[object Object]") {

А кто будет отлавливать исключения?

Когда нет совпадения, что скопировать, то должен возвращаться исходный объект без изменении (не так ли?), а у вас выкидывает исключение!

Интересный способ проверки объекта путём приведения к примитиву! А если такое приведение не возможно, то выкидывает исключение!

Фактически вы ничего не меняете в объекте, зачем нам нужен новый объект, в котором ничего не изменено. Это очень не очевидное API, которое способствует ошибкам. Используйте immer, в котором происходит копирование только при реальных изменениях! В отличие от вашего небезопасного кода, в immer исходный объект не изменяется и при одинаковых аргументах, выдаётся одинаковый результат.

Вот пример...
<script src="https://unpkg.com/immer/dist/immer.umd.js"></script>
<script>

//Например есть массив arr_1. Требуется сделать его копию и
//изменить подмассив по адресу arr_1[2][2][1]. 

const arr_1 = [
    0,
    [1, 2, 3],
    [ 4,
        [5, 6],
        [
            [7, 8],
            [ 9, 10 ]
        ],
    ]
];

//Поэтому в функцию immer.produce передаю ссылки на
//оригинальный массив и функцию, в которой изменяется
//подмассив. Функция возвращает копию реальных изменении.
const copy = immer.produce(arr_1, arr_1 => {
    const link = arr_1[2][2][1];
    // реально меняем подмассив
    link.push(40);
});

console.log(copy);

//Можно сделать проверки

console.log(arr_1 === copy); // false
console.log(arr_1[1] === copy[1]); // true — массивы равны потому что он не будет затронут, поэтому нет смысла его копировать.
console.log(arr_1[2] === copy[2]);  // false — массив был скопирован потому что является предком изменяемого массива
console.log(arr_1[2][2] === copy[2][2]); // false
console.log(arr_1[2][2][0] === copy[2][2][0]); // true

</script> Смотри в консоли

Andrew K 31.10.2019 18:45

Цитата:

Сообщение от Malleys (Сообщение 514423)
А кто будет отлавливать исключения?
Когда нет совпадения, что скопировать, то должен возвращаться исходный объект без изменении

Спасибо за комментарий. Немного переписал функцию. Теперь если целевого объекта нет в объекте данных, то возвращает оригинальный объект данных.


Цитата:

Сообщение от Malleys (Сообщение 514423)
Фактически вы ничего не меняете в объекте, зачем нам нужен новый объект, в котором ничего не изменено. Это очень не очевидное API, которое способствует ошибкам. Используйте immer, в котором происходит копирование только при реальных изменениях! В отличие от вашего небезопасного кода, в immer исходный объект не изменяется и при одинаковых аргументах, выдаётся одинаковый результат.

По логике моей функции сначала создаётся копия объекта данных, а затем меняется. Но сейчас мне кажется вы правы и следует сделать еще один аргумент и сразу передавать изменённый объект данных.

А immer мне не подходит потому что в моём случае не известен полный путь до объекта, который будет изменён.

Andrew K 07.11.2019 17:02

Обновил функцию. Теперь она получает объект, который нужно скопировать, затем объект, который нужно найти и объект, на который нужно заменить объект из второго параметра. Возвращает неизменяемую копию.

Malleys 08.11.2019 02:37

Цитата:

Сообщение от Andrew K
Возвращает неизменяемую копию.

Всё изменяется!
Вот настоящий неизменяемый объект...
var a = Object.freeze({
	id: Object.freeze({ value: 60 }),
	string: Object.freeze({ value: Object.freeze(["abc", 123]) })
});

// попробуем изменить
a.id.value = 77;

// не изменилось, а у вас изменяется
alert(a.id.value);


После вашей функции даже копия настоящего неизменяемого объекта становится изменяемой!

Цитата:

Сообщение от Andrew K
// Скопировать объект и вставить как значение возвращаемого объекта
newData = Object.assign({}, mainData);

Может стоит учитывать исходный конструктор при создании объекта? А то был неизменяемый объект без конструктора, а стал изменяемым и с конструктором...
var a = {
	__proto__: null,
	id: { value: 60 }
};


После вашей функции происходит неожиданная смена конструктора!

Объект без конструктора вы можете отсеять так...
mainData instanceof Object


Цитата:

Сообщение от Andrew K
// Если это массив...
if(toString.call(mainData) === "[object Array]") {

А если это структура, которая была унаследована от класса Array. Почему её нельзя использовать? Может стоит определять так...
mainData instanceof Array


А если это типизированный массив?

Andrew K 16.11.2019 14:27

Цитата:

Сообщение от Malleys
Всё изменяется!

Я использую свою функцию в сценариях и она отрабатывает так, как нужно. Может вы пытались сделать копию объекта с вложенными объектами? В этом случае вложенные объекты будут изменяемыми потому что функция не делает глубокую копию а только заменяет один объект на другой и делает копии элементов вверх по иерархии.

Вы правы насчет того, что если в копируемом объекте установлен другой прототип, то он будет заменён на Object. Как я понимаю проще всего всего в этом случае копировать через деструктуризацию:
{...newData}

Но мне это пока не требуется. Существующий код мне подходит. Так же не использую в работе типизированные массивы.


Часовой пояс GMT +3, время: 19:22.