Pull to refresh

Comments 37

let originalObject = {a: 1, b: 2, c: {d: 3, e: 4}};
let clonedObject = JSON.parse(JSON.stringify(originalObject));

clonedObject.c.d = 7;

console.dir(originalObject); // {"a":1,"b":2,"c":{"d":3,"e":4}}
console.dir(clonedObject); // {"a":1,"b":2,"c":{"d":7,"e":4}}


И зачем огород городить? :)

Другая тема — копирование методов…
Не знал про данный сервис для проверки скорости функций через бечмарк, буду юзать теперь, спасибо за отзыв )
Разница не всегда такая большая.
Вопрос ещё и в том, а нужна ли этой функции высокая производительность в каждом конкретном случае.
Иногда сериализация/десериализация таки самый подходящий путь.
Статья полезна для понимания как хранятся «ссылочные» типы данных, а что касается глубокого клонирования, то я еще не встречал ничего лучше:
const obj = { ... }
const clObj = JSON.parse(JSON.stringify(obj))
Комментарием выше пользователь Sombressoul сделал фаст-тест функции, которую я предложил и через JSON.parse, огромное спасибо ему за это. Как оказалась JSON.parse уступает в производительности в 10 раз. Ссылка натест

JSON не может содержать функции. А значит, склонировать без потерь получится не любой объект...

Вероятно, у вас не очень хороший кругозор. Потому что клонирование через JSON хорошо ровно одним — простотой реализации на коленке. Всем остальным плохо.
не встречал ничего лучше
Навскидку:

JSON.parse(JSON.stringify(undefined));

let d = new Date();
JSON.parse(JSON.stringify(d));

let child = {}, parent = {};
child.parent = parent; parent.child = child;
JSON.parse(JSON.stringify(child));

Для простых случаев вполне подойдет. Однако мы модем клонировать и пользщовательские объекты модем клонировать их вместе с методами, восстанавливать конструктор и соответственно прототип объекта

Как бы сказать помягче?
На выходе из вашей функции получается совсем не то, что должно быть.


Z object after cloning:  { a: [ 1, 2, 3 ] }
Y object:  { a: { '0': 1, '1': 2, '2': 3 }, addnlProp: { fd: 45 } }
Да, полностью согласен, есть недоработка работы с массивом. Хоть и писал и писал ее для объекта, но про такое как массив не стоило забывать. Спасибо, за отличный тык носом )
Вообще, прошу прощения за немного злобный комментарий, но перед тем, как учить, желательно убедиться, что научишь хорошему.
Не думаю, что вы со зла пишете )
Наоборот критика — шикарно, особенно, когда конструктивная. В комментариях мне указали на мои ошибки, где я не прав, даже предложили как улучшить метод для клонирования и это здорово. Во время обсуждения мы учимся и проверяем свои силы. Так что все окей
Ваш комментарий меня побудил немного изменить функция для клонирования
function deepClone(obj) {
  const test1 = { a: { d: [1, 2, 3]}, b: 5 };
const test2 = deepClone(test1);
test2.a.d.push('arr')
test2.b = 9;
console.log('test1 object: ', test1);
console.log('test2 object: ', test2);

function deepClone(obj) {
  const clObj = {};
  for(const i in obj) {
    if (obj[i] instanceof Object && !(obj[i] instanceof Array)) {
      clObj[i] = deepClone(obj[i]);
      continue;
    }
    clObj[i] = obj[i];
  }
  return clObj;
}

Добавив одно условие, я скопировал массив верно, но при этом он остался зависимым от массива из другого объекта. Хотя подобное следовало ожидать, но все же интересная особенность

image

Все таки добью эту функцию, чтобы работала корректно не только с объектами, а как минимум еще с массивом. После чего внесу правки в статью. Еще раз, спасибо вам за отзыва )
Вы не «скопировали массив верно». Это тот же самый массив.

По хорошему, Вам нужен вызов `obj[i].map(deepClone)`, в массиве могут быть не только скаляры.
Я имею ввиду структура массива осталось верной, он не перевел его в объект изменив числовую индексацию на строковую. А так да, ссылается на один и тот же массив.
Отличная идея с map, нужно попробовать ваш вариант на практике и посмотреть на результат, спасибо )
Не за что. :)

Ещё один совет: когда наиграетесь с этим велосипедом, поищите удовлетворяющую Вас open source альтернативу и используйте её — ни к чему тратить силы на самостоятельную поддержку подобных функций.

На примере deepClone: рано или поздно Вы столкнётесь с циклическими ссылками и начнёте решать эту проблему. Есть несколько способов её решения, Вам придётся выбирать. Потом будут новые проблемы, возможно захочется сохранить прототипы… Путь даже такой элементарной штучки довольно долог. Но в существующих реализациях зачастую весь этот путь уже пройден. И для решения вновь возникающих проблем у Вас есть огромный штат добровольцев(включая Вас).

В общем, если проблема не академическая или «на поиграться», всегда стоит начинать с поиска существующих решений. Жизнь одна, если каждый будет распыляться на подобные мелочи. ни у кого не хватит времени создать что-то стоящее.

Хм, растёкся я тут мыслью по древу. извините.
мыслью по древу

Мысью. Т.е. — белкой.
Боян растекался мысью, а я вот мыслью.

Благодарю, не знал.

Я бы еще рассмотрел кейс клонирования объектов с сохранением их прототипов: объекты могут содержать методы, которые мы хотим иметь и у клонов.

Отличный вариант, нужно подумать над этим. Хорошая пища для размышления, спасибо )
Каждый js-программист наверняка без труда ответит на этот вопрос, но все же скажем: примитивные типы данных передаются в функцию всегда только по значению, а ссылочные всегда только по ссылке.

Я не отношусь к категории "каждый программист" потому что думаю что в JS все параметры передаются в функцию по значению. Просто для объектов в качестве значения выступает ссылка на объект. Вы внутри функции можете поменять поля в этом объекте. Но не сможете присвоить параметру новое значение.


Второй момент про алгоритм клонирования. Вцелом направление правильное, на как минимум нужно учес еще и клонирование Array. Хотя если по-хорошему то нужно еще как-то работать с прототипами а также и с другими типами JS


То есть если у Вас объект Dog то его кот также должен быть объектом типа Dog

Да полностью согласен с вами, нужно учитывать множество факторов при алгоритма клонирования. Хочу отметить, что аргументы в функцию все таки передаются двумя способами, на мой взгляд. Просто по ссылке они передаются не явным образом. Мы не можем явно передать аргумент данным способом как это делается в C# или C++, та и в PHP. Это как преобразование типов в JS — компилятор автоматически это сделает все за нас и преобразует в нужный тип, если это будет возможно

Способ один — значение. В качестве значения используется ссылка. Тем кто не программировал на более древних языках (Пскаль, Си, PL/1) это разница не совсем ясна
Еси переменная передается по ссылке то ее значение меняется если его изменить внутри функции. В JS Вы можете изменить сам объект и это будет видно извне.


Но если вы сделаете так


var obj0 = {}
function(obj1) {
obj1 = {}
return obj1
}
obj0 !== obj1 // true


Если бы объект передалася по ссылке то


obj0 === obj1 // true

Просто по ссылке они передаются неявным образом.

Передача переменной по ссылке на примере php: 3v4l.org/TQXg1

В javascript ничего подобного нет, если вы что-то присваиваете переменной, старое значение теряется.
Просто для объектов передаётся значение указателя на тот же объект, что позволяет мутировать этот объект, но не позволяет изменить саму переменную.
let num = 10;
let obj = { a: 5, b: 6 };
Как видим, в первом случае мы перезаписали значение переменной, а во втором расширили объект.
Точно расширили?
function insertValToArr(arr, val) {
  const newArr = [];
  arr.forEach((value, ind) => { newArr[ind] = value});
  newArr.push(val);
  return newArr;
}

forEach здесь не нужен, лучше использовать map:
function insertValToArr(arr, val) {
  const newArr = arr.map((value) => value);
  newArr.push(val);
  return newArr;
}

А ещё лучше сократить до однострочника:
const insertValToArr = (arr, val) => arr.map((value) => value).concat(val);


И в JS есть 7 примитивных типов данных и всего один ссылочный — Object (MDN). Array, Function, Maps, Sets и т. д. — это всё реализации объекта.
У вас отличные варианты для реализации функции insertValToArr. От себя могу добавить, что можно подобное сделать и через spread, но данный вариант скорее подойдет для одномерных массивов.

function insertValToArr(arr, val) {
  return [...arr, val];
}
Да, про спреды я не подумал.
А вариант с map, действительно легко адаптируется для клонирования многомерных массивов:

const insertValToArr = (param) => (
    Array.isArray(param) ? param.map(insertValToArr) : param
);
Добавлю еще, что проверка по `instanceof` не всегда будет `true` для объектов.

Мы всегда можем создать объект без прототипа через Object.create. Так как у такого объекта не будет прототипа Object, то и логично, что instanceof не найдет его там.

const a = Object.create(null);
console.log(a instanceof Object) // false
const b = {};
console.log(b instanceof Object) // true
Как тогда определить что это «объект»?
Sign up to leave a comment.

Articles