Кирилл Мыльников

Фронтенд-разработчик

Всем привет, я Кирилл, фронтенд-разработчик в Usetech.

Сегодня мы поговорим о глубоком и поверхностном клонировании объекта, увидим различные примеры и способы его реализации, а также разберем отличия, преимущества и недостатки данного подхода, обратим внимание на новый метод комплексного глубокого клонирования – structuredClone.

Глубокое клонирование:

Клонирование поверхности:

  • Оператор спреда

  • Объект.назначить()

  • Объект.создать()

Знаете ли вы, что теперь в JavaScript есть встроенный способ создания глубоких копий объекта?

Итак, мы говорим о structuredClone это функция, встроенная в среду JavaScript:

const person = {
  name: "Ivan",
  date: new Date(123),
  friends: ["John"]
}
// клон объекта
const copiedPerson = structuredClone(person)

Обратите внимание, что мы скопировали не только объект, но и вложенный массив, и даже объект Date.

Как видите, все работает, как и ожидалось.

copiedPerson.friends // ["John"]
copiedPerson.date // Date: Wed Dec 31 1969 16:00:00
copiedPerson.friends === person.friends // false

Метод structuredClone может выполнять следующие задачи:

  • Клонировать бесконечно вложенные объекты и массивы;

  • Клонировать циклические ссылки;

  • Клонируйте различные типы JS, такие как Date, Error, RegExp, ArrayBuffer, Blob, File, ImageData И другой;

  • Передача всех переданные объекты.

Даже такой сумасшедший пример сработал так, как мы хотели.

const obj = {
  set: new Set([1123, 36, 3, 4, 4]),
  map: new Map([[133, 2222]]),
  regex: /foo/,
  deep: { array: [ new File(someBlobData, 'test.txt') ] },
  error: new Error('Error!')
}
obj.circular = obj 
// ✅ Все хорошо, клонировали весь объект.
const clonedObj = structuredClone(obj)

Почему бы просто не взять и не катить объект?

Важно понимать, какого типа клонирование мы хотим: глубокое или поверхностное. Если нам нужно создать поверхностный клон, т.е. копию, которая не копирует вложенные объекты или массивы, мы можем это сделать Spread оператор.

Пример:

const obj = {
  title: "Builder.io Conf",
}
// ✅ нет проблем, нет вложенных массивов и объектов
const shallowCopy = {...obj}

Есть еще один поверхностный метод клонирования:

const shallowCopy = Object.assign({}, obj);
const shallowCopy = Object.create(obj);

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

const personObj = {
  title: "Hello world",
  date: new Date(123),
  friends: ["John"]
}
const shallowCopy = {...calendarEvent}
// 🚩 Мы только что добавили “Bob” и к копии и оригиналу
shallowCopy.friends.push("Bob")
// 🚩 Мы только что изменили время у клона и оригинала
shallowCopy.date.setTime(456)

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

ЧИТАТЬ   16 коварных деталей, которые разрушат даже самый безупречный образ

И может делать глубокое клонирование через JSON.parse(JSON.stringify(obj))?

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

Возьмите этот объект в качестве примера:

const person = {
  name: "Ivan",
  date: new Date(123),
  friends: ["John"]
}
// 🚩 Выполним глубокое клонирование.
const problematicPersonCopy = JSON.parse(JSON.stringify(person))

Если мы посмотрим на результат problematicPersonCopyмы увидим следующее:

{
  name: "Ivan",
  date: "1970-01-01T00:00:00.123Z"
  friends: ["John"]
}

Это немного отличается от того, что мы хотели: date должен быть объектом Date, а не строкой. Это произошло потому, что JSON.stringify() может обрабатывать только базовые объекты, массивы, примитивы. Со всеми другими типами он может вести себя не так, как вы ожидаете. Например, дата будет преобразована в строку, а набор — в {}. JSON.stringify() полностью игнорирует некоторые вещи, такие как undefined или functions.

Пример:

const obj = {
  set: new Set([1, 3, 3]),
  map: new Map([[1, 2]]),
  regex: /foo/,
  deep: { array: [ new File(someBlobData, 'file.txt') ] },
  error: new Error('Hello!')
}
const veryProblematicObjCopy = JSON.parse(JSON.stringify(obj))

Что мы получим:

{
  "set": {},
  "map": {},
  "regex": {},
  "deep": {
    "array": [
      {}
    ]
  },
  "error": {},
}

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

Рассмотрим следующий метод через lodash.cloneDeep.

Сегодня cloneDeep функция lodash очень распространенное решение этой проблемы.

И действительно работает так, как ожидалось:

import cloneDeep from 'lodash/cloneDeep'
const person = {
  name: "Ivan",
  date: new Date(123),
  friends: ["John"]
}
// ✅ Все хорошо!
const clonedEvent = cloneDeep(person)

Но здесь есть оговорка. В зависимости от расширения Стоимость импортав моей IDE, которая отображает вес в килобайтах того, что я импортирую, эта функция занимает всего 17,4 КБ.

ffb1889348f77036e724dd767e82703b

Если не придавать значения и сразу импортировать из библиотеки, то мы увидим, насколько импорт стал сложнее. И это только для одной функции. cloneDeep.

656c37ab35926e4a4ef7858d847dff51

Но перетаскивать целую библиотеку для глубокого клонирования пока не нужно, когда есть structuredClone.

Полифил клонГлубокий

Кроме того, мы всегда можем написать собственный полифил, если это абсолютно необходимо для глубокого клонирования.

ЧИТАТЬ   Трамп обвинил Байдена в получении взяток от украинской компании

Пример полифила:

const deepClone = (obj) => {
  if (obj === null) return null
  const clone = Object.assign({}, obj)
  Object.keys(clone).forEach(
    (key) =>
      (clone[key] =
        typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key])
  )
  if (Array.isArray(obj)) {
    clone.length = obj.length
    return Array.from(clone)
  }
  return clone
}
const a = { foo: 'bar', obj: { a: 1, b: 2 } }
const b = deepClone(a) // a !== b = true, a.obj === b.obj = false

Реализация довольно простая, но если вы считаете, что я что-то упустил, вы можете отредактировать и рассказать об этом в комментариях.

Что в structuredClone нельзя клонировать?

1. Функции

Они вызовут исключение DataCloneError.

// 🚩 Ошибка!
structuredClone({ fn: () => { } })

2. Узлы ДОУ

Также вызывает исключение DataCloneError.

// 🚩 Ошибка!
structuredClone({ el: document.body })

3. Дескрипторы свойств, сеттеры и геттеры. А также подобные функции, такие как метаданные, не клонируются

Например, с геттером клонируется результирующее значение, но не сама функция геттера.

structuredClone({ get foo() { return 'bar' } })

4. Прототипы объектов.

Если вы запланируете экземпляр myClass, клонированный объект больше не будет известен как экземпляр этого класса (но будут клонированы все фактические свойства этого класса).

class MyClass { 
  foo = 'bar' 
  myMethod() { /* ... */ }
}
const myClass = new MyClass()
const cloned = structuredClone(myClass)
// Becomes: { foo: 'bar' }
cloned instanceof myClass // false

Полный список поддерживаемых типов. Проще говоря, то, чего нет в списке типов, клонировать нельзя:

Встроенные модули JS: Array, ArrayBuffer, Boolean, DataView, Date, Error, Map и т.д.

Типы ошибок: Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError и т.д.

Поддержка браузера и исполнение.

И вот лучшая часть – structuredClone поддерживается во всех основных браузерах, даже в Node.js и Deno.

Поддержка браузера: Источник: Не беспокоить

f116150fde9bb5878883a947833709fa

Мы рассмотрели глубокое и поверхностное клонирование, проанализировали примеры и методы и выделили плюсы и минусы. Если я что-то упустил, вы можете добавить меня в комментарии или рассказать о своем опыте и поделиться примерами.

ЧИТАТЬ   Amazfit выпускает часы Pop 3S с AMOLED-дисплеем, звонками и 12-дневным временем автономной работы

Source

От admin