Задать вопрос
@ModelViewWithoutControl

Как записать рекурсию (функц. глубокого копирования) в виде цикла?

Учу javascript, и у меня возник вопрос. Допустим, есть некий объект:

let car = {
	name: "Title",
	color: "yellow",
	owns: {
		1:  "Иван",
		2:  "Джон",
	},
}

И я хочу клонировать этот объект в другую переменную. Есть много способов сделать это: и с помощью сторонних библиотек, и в виде рекурсии. Но если это цикл, то как правильнее всего будет это сделать?
  • Вопрос задан
  • 248 просмотров
Подписаться 1 Простой Комментировать
Решения вопроса 1
0xD34F
@0xD34F
Используйте стек.

Если, обрабатывая данные, натыкаетесь на объект, надо сохранить в стек текущее состояние - ссылку на копируемые данные, индекс текущего элемента, ссылку на объект, куда копируете; затем установить новое состояние - копируемыми данными будет массив пар ключ-значение встреченного объекта, индекс обнуляется (на самом деле надо выставить -1, нулевое значение будет получено при переходе к следующей итерации; конечно, можно и буквально 0 выставлять, но тогда придётся усложнить работу с индексом - тут увеличиваем, а тут нет), пустой объект в качестве копии.

Если данные закончились, надо восстановить состояние из стека.

Условий, при которых возможно продолжение цикла, будет два - кроме наличия необработанных данных ещё и непустой стек.

function clone(value) {
  const clone = {};
  const stack = [];

  for (
    let i = 0, source = [ [ '', value ] ], target = clone;
    i < source.length || stack.length;
    i++
  ) {
    if (i === source.length) {
      [ i, source, target ] = stack.pop();
    } else {
      const [ k, v ] = source[i];
      const isObject = v instanceof Object;

      target[k] = isObject ? v.constructor() : v;

      if (isObject) {
        stack.push([ i, source, target ]);
        [ i, source, target ] = [ -1, Object.entries(v), target[k] ];
      }
    }
  }

  return clone[''];
}

Или, если в копируемом объекте могут быть циклические ссылки или какой-то из вложенных объектов встречается несколько раз и так же должно быть и в копии, то надо запоминать встреченные объекты, чтобы не обрабатывать их повторно - воспользуемся Map'ом, где ключами будут оригинальные объекты, а значениями их копии.

Как выглядит получение копии объекта: проверяем, встречался ли указанный объект ранее, если нет - создаём новый объект, сохраняем его в Map, а также кладём его вместе с оригиналом в стек, для последующей обработки; достаём из Map'а копию.

Крутим цикл, пока стек не пуст, на каждой итерации достаём из стека объект и его копию, перебираем свойства оригинала, записываем в копию копии значений.

function clone(value) {
  const stack = [];
  const clones = new Map;
  const getClone = val => val instanceof Object
    ? (clones.has(val) || stack.push([ val, clones.set(val, val.constructor()).get(val) ]),
       clones.get(val))
    : val;

  for (getClone(value); stack.length;) {
    const [ source, target ] = stack.pop();
    Object.entries(source).forEach(n => target[n[0]] = getClone(n[1]));
  }

  return getClone(value);
}

как правильнее всего будет это сделать?

Перестать заниматься ерундой и начать использовать готовые решения - structuredClone или cloneDeep, например.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 2
zkrvndm
@zkrvndm
Архитектор решений
Самый простой вариант клонировать обьект это конвертировать его в JSON, а потом обратно. Другое дело, что это не подойдет, если в объекте есть типы данных, которые нельзя представить внутри JSON. Вам обязательно цикл нужен?
Ответ написан
@ModelViewWithoutControl Автор вопроса
Придумался вот такой вариант:
let clone = {};
for (key in car) {
	if (typeof car[key] === "object") {
		clone[key] = Object.assign({}, car[key]);
	}
	clone[key] = car[key];
}
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы