Cloning objects in javascript

Object.assign()

The Object.assign() static method copies all enumerable own properties from one or more source objects to a target object. It returns the modified target object.

Try it

Syntax

Parameters

The target object — what to apply the sources’ properties to, which is returned after it is modified.

The source object(s) — objects containing the properties you want to apply.

Return value

Description

Properties in the target object are overwritten by properties in the sources if they have the same key. Later sources’ properties overwrite earlier ones.

The Object.assign() method only copies enumerable and own properties from a source object to a target object. It uses [[Get]] on the source and [[Set]] on the target, so it will invoke getters and setters. Therefore it assigns properties, versus copying or defining new properties. This may make it unsuitable for merging new properties into a prototype if the merge sources contain getters.

For copying property definitions (including their enumerability) into prototypes, use Object.getOwnPropertyDescriptor() and Object.defineProperty() instead.

Both String and Symbol properties are copied.

In case of an error, for example if a property is non-writable, a TypeError is raised, and the target object is changed if any properties are added before the error is raised.

Читайте также:  Sans serif font css code

Note: Object.assign() does not throw on null or undefined sources.

Examples

Cloning an object

const obj =  a: 1 >; const copy = Object.assign(>, obj); console.log(copy); // 

Warning for Deep Clone

For deep cloning, we need to use alternatives like structuredClone() , because Object.assign() copies property values.

If the source value is a reference to an object, it only copies the reference value.

const obj1 =  a: 0, b:  c: 0 > >; const obj2 = Object.assign(>, obj1); console.log(obj2); // < a: 0, b: < c: 0 >> obj1.a = 1; console.log(obj1); // < a: 1, b: < c: 0 >> console.log(obj2); // < a: 0, b: < c: 0 >> obj2.a = 2; console.log(obj1); // < a: 1, b: < c: 0 >> console.log(obj2); // < a: 2, b: < c: 0 >> obj2.b.c = 3; console.log(obj1); // < a: 1, b: < c: 3 >> console.log(obj2); // < a: 2, b: < c: 3 >> // Deep Clone const obj3 =  a: 0, b:  c: 0 > >; const obj4 = structuredClone(obj3); obj3.a = 4; obj3.b.c = 4; console.log(obj4); // < a: 0, b: < c: 0 >> 

Merging objects

const o1 =  a: 1 >; const o2 =  b: 2 >; const o3 =  c: 3 >; const obj = Object.assign(o1, o2, o3); console.log(obj); // console.log(o1); // < a: 1, b: 2, c: 3 >, target object itself is changed. 

Merging objects with same properties

const o1 =  a: 1, b: 1, c: 1 >; const o2 =  b: 2, c: 2 >; const o3 =  c: 3 >; const obj = Object.assign(>, o1, o2, o3); console.log(obj); // 

The properties are overwritten by other objects that have the same properties later in the parameters order.

Copying symbol-typed properties

const o1 =  a: 1 >; const o2 =  [Symbol("foo")]: 2 >; const obj = Object.assign(>, o1, o2); console.log(obj); // < a : 1, [Symbol("foo")]: 2 >(cf. bug 1207182 on Firefox) Object.getOwnPropertySymbols(obj); // [Symbol(foo)] 

Properties on the prototype chain and non-enumerable properties cannot be copied

const obj = Object.create( // foo is on obj's prototype chain.  foo: 1 >,  bar:  value: 2, // bar is a non-enumerable property. >, baz:  value: 3, enumerable: true, // baz is an own enumerable property. >, >, ); const copy = Object.assign(>, obj); console.log(copy); // 

Primitives will be wrapped to objects

const v1 = "abc"; const v2 = true; const v3 = 10; const v4 = Symbol("foo"); const obj = Object.assign(>, v1, null, v2, undefined, v3, v4); // Primitives will be wrapped, null and undefined will be ignored. // Note, only string wrappers can have own enumerable properties. console.log(obj); // 

Exceptions will interrupt the ongoing copying task

const target = Object.defineProperty(>, "foo",  value: 1, writable: false, >); // target.foo is a read-only property Object.assign(target,  bar: 2 >,  foo2: 3, foo: 3, foo3: 3 >,  baz: 4 >); // TypeError: "foo" is read-only // The Exception is thrown when assigning target.foo console.log(target.bar); // 2, the first source was copied successfully. console.log(target.foo2); // 3, the first property of the second source was copied successfully. console.log(target.foo); // 1, exception is thrown here. console.log(target.foo3); // undefined, assign method has finished, foo3 will not be copied. console.log(target.baz); // undefined, the third source will not be copied either. 

Copying accessors

const obj =  foo: 1, get bar()  return 2; >, >; let copy = Object.assign(>, obj); console.log(copy); // // The value of copy.bar is obj.bar's getter's return value. // This is an assign function that copies full descriptors function completeAssign(target, . sources)  sources.forEach((source) =>  const descriptors = Object.keys(source).reduce((descriptors, key) =>  descriptors[key] = Object.getOwnPropertyDescriptor(source, key); return descriptors; >, >); // By default, Object.assign copies enumerable Symbols, too Object.getOwnPropertySymbols(source).forEach((sym) =>  const descriptor = Object.getOwnPropertyDescriptor(source, sym); if (descriptor.enumerable)  descriptors[sym] = descriptor; > >); Object.defineProperties(target, descriptors); >); return target; > copy = completeAssign(>, obj); console.log(copy); // < foo:1, get bar() < return 2 >> 

Specifications

Browser compatibility

BCD tables only load in the browser

See also

Источник

Копирование объектов и ссылки

Одно из фундаментальных отличий объектов от примитивов заключается в том, что объекты хранятся и копируются «по ссылке», тогда как примитивные значения: строки, числа, логические значения и т.д. – всегда копируются «как целое значение».

Это легко понять, если мы немного заглянем под капот того, что происходит, когда мы копируем значение.

Давайте начнём с примитива, такого как строка.

Здесь мы помещаем копию message во phrase :

let message = "Привет!"; let phrase = message;

В результате мы имеем две независимые переменные, каждая из которых хранит строку «Привет!» .

Вполне очевидный результат, не так ли?

Переменная, которой присвоен объект, хранит не сам объект, а его «адрес в памяти» – другими словами, «ссылку» на него.

Давайте рассмотрим пример такой переменной:

И вот как это на самом деле хранится в памяти:

Объект хранится где-то в памяти (справа от изображения), в то время как переменная user (слева) имеет лишь «ссылку» на него.

Мы можем думать о переменной объекта, такой как user , как о листе бумаги с адресом объекта на нем.

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

Теперь вот почему это важно.

При копировании переменной объекта копируется ссылка, но сам объект не дублируется.

let user = < name: "John" >; let admin = user; // копируется ссылка

Теперь у нас есть две переменные, каждая из которых содержит ссылку на один и тот же объект:

Как вы можете видеть, все ещё есть один объект, но теперь с двумя переменными, которые ссылаются на него.

Мы можем использовать любую переменную для доступа к объекту и изменения его содержимого:

let user = < name: 'John' >; let admin = user; admin.name = 'Pete'; // изменено по ссылке из переменной "admin" alert(user.name); // 'Pete', изменения видны по ссылке из переменной "user"

Это как если бы у нас был шкафчик с двумя ключами, и мы использовали один из них ( admin ), чтобы войти в него и внести изменения. А затем, если мы позже используем другой ключ ( user ), мы все равно открываем тот же шкафчик и можем получить доступ к изменённому содержимому.

Сравнение по ссылке

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

Например, здесь a и b ссылаются на один и тот же объект, поэтому они равны:

let a = <>; let b = a; // копирование по ссылке alert( a == b ); // true, обе переменные ссылаются на один и тот же объект alert( a === b ); // true

И здесь два независимых объекта не равны, даже если они выглядят одинаково (оба пусты):

let a = <>; let b = <>; // два независимых объекта alert( a == b ); // false

Для сравнений типа obj1 > obj2 или для сравнения с примитивом obj == 5 объекты преобразуются в примитивы. Очень скоро мы изучим, как работают преобразования объектов, но, по правде говоря, такие сравнения требуются очень редко и обычно они появляются в результате ошибок программиста.

Клонирование и объединение, Object.assign

Итак, копирование объектной переменной создаёт ещё одну ссылку на тот же объект.

Но что, если нам всё же нужно дублировать объект? Создать независимую копию, клон?

Это тоже выполнимо, но немного сложнее, потому что в JavaScript для этого нет встроенного метода. Но на самом деле в этом редко возникает необходимость, копирования по ссылке в большинстве случаев вполне хватает.

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

let user = < name: "John", age: 30 >; let clone = <>; // новый пустой объект // давайте скопируем все свойства user в него for (let key in user) < cloneCloning objects in javascript = userCloning objects in javascript; >// теперь clone это полностью независимый объект с тем же содержимым clone.name = "Pete"; // изменим в нём данные alert( user.name ); // все ещё John в первоначальном объекте

Также мы можем использовать для этого метод Object.assign.

Object.assign(dest, [src1, src2, src3. ])
  • Первый аргумент dest — целевой объект.
  • Остальные аргументы src1, . srcN (может быть столько, сколько необходимо) являются исходными объектами
  • Метод копирует свойства всех исходных объектов src1, . srcN в целевой объект dest . Другими словами, свойства всех аргументов, начиная со второго, копируются в первый объект.
  • Возвращает объект dest .

Например, мы можем использовать его для объединения нескольких объектов в один:

let user = < name: "John" >; let permissions1 = < canView: true >; let permissions2 = < canEdit: true >; // копируем все свойства из permissions1 и permissions2 в user Object.assign(user, permissions1, permissions2); // теперь user =

Если скопированное имя свойства уже существует, оно будет перезаписано:

let user = < name: "John" >; Object.assign(user, < name: "Pete" >); alert(user.name); // теперь user =

Мы также можем использовать Object.assign для замены цикла for..in для простого клонирования:

let user = < name: "John", age: 30 >; let clone = Object.assign(<>, user);

Он копирует все свойства user в пустой объект и возвращает его.

Также существуют и другие методы клонирования объекта. Например, с использованием оператора расширения clone = <. user>, рассмотренного далее в учебнике.

Вложенное клонирование

До сих пор мы предполагали, что все свойства user примитивныe. Но свойства могут быть и ссылками на другие объекты. Что с ними делать?

let user = < name: "John", sizes: < height: 182, width: 50 >>; alert( user.sizes.height ); // 182

Теперь недостаточно просто скопировать clone.sizes = user.sizes , потому что user.sizes – это объект, он будет скопирован по ссылке. Таким образом, clone и user будут иметь общий объект sizes :

let user = < name: "John", sizes: < height: 182, width: 50 >>; let clone = Object.assign(<>, user); alert( user.sizes === clone.sizes ); // true, тот же объект // user и clone обладают общим свойством sizes user.sizes.width++; // изменяем свойства в первом объекте alert(clone.sizes.width); // 51, видим результат в другом

Чтобы исправить это, мы должны использовать цикл клонирования, который проверяет каждое значение userCloning objects in javascript и, если это объект, тогда также копирует его структуру. Это называется «глубоким клонированием».

Мы можем реализовать глубокое клонирование, используя рекурсию. Или, чтобы не изобретать велосипед заново, возьмите готовую реализацию, например _.cloneDeep(obj) из библиотеки JavaScript lodash.

Также мы можем использовать глобальный метод structuredClone(), который позволяет сделать полную копию объекта. К сожалению он поддерживается только современными браузерами. Здесь можно ознакомиться с поддержкой этого метода.

Важным побочным эффектом хранения объектов в качестве ссылок является то, что объект, объявленный как const , может быть изменён.

const user = < name: "John" >; user.name = "Pete"; // (*) alert(user.name); // Pete

Может показаться, что строка (*) вызовет ошибку, но, это не так. Значение user это константа, оно всегда должно ссылаться на один и тот же объект, но свойства этого объекта могут свободно изменяться.

Другими словами, const user выдаст ошибку только в том случае, если мы попытаемся задать user=. в целом.

Тем не менее, если нам действительно нужно создать постоянные свойства объекта, это тоже возможно, но с использованием совершенно других методов. Мы затронем это в главе Флаги и дескрипторы свойств.

Итого

Объекты присваиваются и копируются по ссылке. Другими словами, переменная хранит не «значение объекта», а «ссылку» (адрес в памяти) на это значение. Таким образом, копирование такой переменной или передача её в качестве аргумента функции копирует эту ссылку, а не сам объект.

Все операции с использованием скопированных ссылок (например, добавление/удаление свойств) выполняются с одним и тем же объектом.

Чтобы создать «реальную копию» (клон), мы можем использовать Object.assign для так называемой «поверхностной копии» (вложенные объекты копируются по ссылке) или функцию «глубокого клонирования», такую как _.cloneDeep(obj).

Источник

Оцените статью