JavaScript метод Object.assign()
Определение и применение
JavaScript метод assign() позволяет произвести поверхностное копирование значений всех перечислимых
собственных свойств и методов из одного или нескольких исходных объектов в целевой объект. Метод также копирует
свойства, ключом которых является символ (Symbol).
Под перечислимыми свойствами стоит понимать те, атрибут enumerable которых имеет значение
true, вследствие чего они будут перечислимы в циклах, например в цикле for...in. Атрибуты в
свою очередь описываются дескрипторами свойств – это обычные объекты JavaScript, которые описывают
атрибуты и значение свойства. Подробную информацию о дескрипторах свойств и атрибутах вы можете получить в описании
метода
defineProperty(), который позволяет определить новое или изменить существующее свойство объекта, описывая его дескрипторами.
Object.assign(target) допустим без источников. null и undefined в качестве цели
вызывают TypeError. Копируются значения, а не дескрипторы: чтение вызывает геттеры источника, запись —
сеттеры цели. for...in перечисляет только строковые ключи, хотя assign() также копирует
перечислимые символы. JSON-клонирование подходит только для совместимых с JSON данных: оно теряет функции и часть
значений, преобразует даты в строки и не поддерживает циклы и BigInt. Для поддерживаемых типов можно
использовать structuredClone().
Поддержка браузерами
Спецификация: ecma262
Поддерживается во всех основных браузерах с 2016 года.
| Возможность | Компьютеры | Мобильные | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
assign | 45 | 12 | 34 | 32 | 9 | 45 | 34 | 32 | 9 | 5.0 | 45 | 9 |
поддерживаетсяс ограниченияминет поддержкинет данных≤31 — не позднее версии 31
JavaScript синтаксис
// копирование из одного исходного объекта
Object.assign( target, source );
// копирование из нескольких исходных объектов
Object.assign( target, ...sources );
Спецификация
Значения параметров
| Параметр | Описание |
|---|---|
target |
Целевой объект в который будут скопированы значения всех перечислимых собственных свойств и методов объекта, или объектов, перечисленных в параметре метода. Обязательное значение. |
source / sources |
Исходный объект или объекты из которых производиться копирование значений всех перечислимых собственных
свойств и методов. Необязательные параметры. Такие источники как null или undefined обрабатываются как пустые объекты и не
добавляют ничего к целевому объекту. Если источник не имеет перечислимых свойств или методов, то он также не
будет добавлен к целевому объекту.
|
Исключения
| Тип исключения | Описание |
|---|---|
TypeError |
Возникает в том случае, если целевой объект равен null или undefined, а также если целевое свойство или метод перезаписывается, а оно доступно только для чтения,
целевой объект при этом может быть изменен, если какие-либо свойства или методы были добавлены до
возникновения подобной ошибки (пример рассмотрен ниже).
|
Примеры, попробуйте сами
Слияние объектов
В этом примере мы рассмотрим как с помощью метода assign() поверхностно скопировать собственные свойства
трех объектов в один объект:
В примере «Слияние трёх объектов» показан результат вызова метода.
Слияние трёх объектов
Измените свойства исходного объекта и сравните результат до и после вызова метода.
index.html
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Слияние трёх объектов</title>
</head>
<body>
<p>Результат выполнения выводится в консоль.</p>
</body>
</html>
main.js
// инициализируем переменные, содержащие объект
let a = { a: 1 },
b = { b: 2 },
c = { c: 3 }
let mergeObjects = Object.assign(a, b, c) // переменная "a" является целевым объектом, а переменные "b" и "c" служат источниками
try {
console.log(mergeObjects)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // переменная содержит значение { a: 1, b: 2, c: 3 }
try {
console.log(a)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // переменная содержит значение { a: 1, b: 2, c: 3 }
Слияние объектов с одинаковыми свойствами
В этом примере мы рассмотрим с Вами как происходит поверхностное копирование объектов, которые имеют одинаковые свойства:
В примере «Перезапись совпадающих ключей» показан результат вызова метода.
Перезапись совпадающих ключей
Измените свойства исходного объекта и сравните результат до и после вызова метода.
index.html
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Перезапись совпадающих ключей</title>
</head>
<body>
<p>Результат выполнения выводится в консоль.</p>
</body>
</html>
main.js
// инициализируем переменные, содержащие объект с одноименными свойствами
let a = { x: 1 },
b = { x: 2 },
c = { x: 3 }
try {
Object.assign(a, b, c)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // переменная "a" является целевым объектом, а переменные "b" и "c" служат источниками
try {
console.log(a)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // переменная содержит значение { x: 3 }
Обратите внимание, что метод .assign() перезаписывает значение свойства в целевом объекте, если они имеют
один и тот же ключ, что и в переданном источнике. Если источники имеют один и тот же ключ (свойство), то более поздние
источники будут перезаписывать более ранние, как в этом примере, сначала переменная "b" перезаписала значение
свойства x переменной "a", а потом переменная "c" перезаписала значение свойства
x переменной "a".
Поверхностное копирование объекта
В следующем примере мы рассмотрим как произвести поверхностное копирование объекта в новый объект:
В примере «Копирование в новый объект» показан результат вызова метода.
Копирование в новый объект
Измените свойства исходного объекта и сравните результат до и после вызова метода.
index.html
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Копирование в новый объект</title>
</head>
<body>
<p>Результат выполнения выводится в консоль.</p>
</body>
</html>
main.js
let student = { name: 'Ivan', family: 'Susanin' }
let shallowClone = Object.assign({}, student)
try {
console.log(shallowClone)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // переменная содержит значение {name: "Ivan", family: "Susanin"}
Проблемы при поверхностном копировании
В этом примере мы с Вами детально рассмотрим как происходит поверхностное копирование элементов объекта, значением которых является объект:
В примере «Общие вложенные объекты» показан результат вызова метода.
Общие вложенные объекты
Измените свойства исходного объекта и сравните результат до и после вызова метода.
index.html
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Общие вложенные объекты</title>
</head>
<body>
<p>Результат выполнения выводится в консоль.</p>
</body>
</html>
main.js
// инициализируем переменную, содержащую объект
let obj = {
a: 1,
b: {
c: 2,
},
}
// осуществляем поверхностное копирование объекта и инициализируем переменную
let newObj = Object.assign({}, obj)
try {
console.log(newObj)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // переменная содержит значение { a: 1, b: { c: 2} }
try {
obj.a = 100
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // изменяем значение свойства объекта obj
try {
newObj.a = 150
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // изменяем значение свойства объекта newObj
try {
console.log(obj)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // переменная содержит значение { a: 100, b: { c: 2} }
try {
console.log(newObj)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // переменная содержит значение { a: 150, b: { c: 2} }
try {
obj.b.c = 200
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // изменяем значение свойства объекта obj
try {
console.log(obj)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // переменная содержит значение { a: 100, b: { c: 200} }
try {
console.log(newObj)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // переменная содержит значение { a: 150, b: { c: 200} }
Важным в понимании работы метода assign() и поверхностного копирования в частности является то, что если
исходное значение свойство является ссылкой на объект, то оно копирует только ссылку на это значение, а не создает
новое значение. Независимо от того где мы изменяем это значение - в новом объекте, или в первоначальном, значение
будет изменено везде. Для осуществления глубокого клонирования нам необходимо использовать другие альтернативы.
Глубокое копирование объекта
Глубокое копирование объекта в отличии от поверхностного дублирует каждый встреченный ею объект, то есть копия и исходный объект не будут разделять общие элементы, поэтому для поддерживаемых выбранным способом типов создаётся независимая копия.
Давайте рассмотрим одно из решений проблемы, с которой мы столкнулись с помощью метода assign() и сделаем
глубокую копию нашего объекта:
В примере «Копирование совместимых с JSON данных» показан результат вызова метода.
Копирование совместимых с JSON данных
Измените свойства исходного объекта и сравните результат до и после вызова метода.
index.html
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Копирование совместимых с JSON данных</title>
</head>
<body>
<p>Результат выполнения выводится в консоль.</p>
</body>
</html>
main.js
// инициализируем переменную, содержащую объект
let obj = {
a: 1,
b: {
c: 2,
},
}
// осуществляем глубокое копирование объекта и инициализируем переменную
let newObj = JSON.parse(JSON.stringify(obj))
try {
console.log(newObj)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // переменная содержит значение { a: 1, b: { c: 2} }
try {
obj.b.c = 200
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // изменяем значение свойства объекта obj
try {
console.log(obj)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // переменная содержит значение { a: 1, b: { c: 200} }
try {
console.log(newObj)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // переменная содержит значение { a: 1, b: { c: 2} }
В этом примере с использованием метода JSON.stringify мы преобразовали значение переменной
obj в строку
JSON, а с помощью метода JSON.parse парсим (разбираем) эту строку и возвращаем новый объект,
соответствующий переданной строке. В результате чего, свойства, значением которых является объект имеют собственные
значения, а не ссылаются на первоначальный объект. Благодаря этим преобразованиям мы получаем новый объект, который
является независимой копией данных этого примера.
Копирование символов
Метод assign() позволяет скопировать свойства, ключом которых является символ (Symbol):
В примере «Копирование символьного ключа» показан результат вызова метода.
Копирование символьного ключа
Измените свойства исходного объекта и сравните результат до и после вызова метода.
index.html
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Копирование символьного ключа</title>
</head>
<body>
<p>Результат выполнения выводится в консоль.</p>
</body>
</html>
main.js
// инициализируем переменную, содержащую объект
let obj = {
[Symbol('test')]: 'test',
name: 'Vasya',
}
let newObj = Object.assign({}, obj)
try {
console.log(obj)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // {name: "Vasya"} — вывод примера не показывает символьные ключи, поэтому они выведены ниже
const keys = Object.getOwnPropertySymbols(newObj)
console.log(keys.map(String))
console.log(newObj[keys[0]])
Нюансы поверхностного копирования
В этом примере мы с Вами рассмотрим некоторые нюансы при поверхностном копировании:
В примере «Примитивы в источниках» показан результат вызова метода.
Примитивы в источниках
Измените свойства исходного объекта и сравните результат до и после вызова метода.
index.html
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Примитивы в источниках</title>
</head>
<body>
<p>Результат выполнения выводится в консоль.</p>
</body>
</html>
main.js
let v1 = null,
v2 = true,
v3 = undefined,
v4 = NaN,
v5 = '',
v6 = Symbol('test'),
v7 = [],
v8 = {},
v9 = Infinity,
v10 = 100,
v11 = 'ok?'
let obj = Object.assign({}, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11)
try {
console.log(obj)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // переменная содержит значение {0: "o", 1: "k", 2: "?"}
Обратите внимание, что только строки могут иметь собственные перечислимые свойства, остальные переменные просто были проигнорированы.
Возникновение исключения
Давайте рассмотрим пример в котором рассмотрим при каких условиях вызывается исключение при работе со свойством
assign():
В примере «Ошибка записи и частичный результат» показан результат вызова метода.
Ошибка записи и частичный результат
Измените свойства исходного объекта и сравните результат до и после вызова метода.
index.html
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Ошибка записи и частичный результат</title>
</head>
<body>
<p>Результат выполнения выводится в консоль.</p>
</body>
</html>
main.js
// задаем пустому объекту новое свойство и инициализируем переменную
let target = Object.defineProperty({}, 'a', {
value: 1, // значение свойства
writable: false, // свойство только для чтения
})
try {
console.log(target)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // {} — свойство a неперечислимое и в выводе не видно
console.log(target.a) // 1
try {
Object.assign(target, { b: 2 }, { c: 3, a: 4, d: 5 })
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // TypeError свойство "a" только для чтения
try {
console.log(target)
} catch (error) {
console.log(error.name + ': ' + error.message) // Показываем ошибку и продолжаем сравнение.
} // {b: 2, c: 3}: свойства до ошибки скопированы, d — нет
console.log(target.a, 'd' in target) // 1 false
В этом примере с использованием метода defineProperty() мы задаем пустому объекту новое свойство только
для чтения и инициализируем этим объектом переменную. После этого мы копируем в целевой объект два объекта, что
приводит к возникновению исключения по той причине, что происходит попытка перезаписать свойство доступное только для
чтения.
Обратите внимание, что те свойства, которые были добавлены до ошибки, которая прервала копирование, были успешно скопированы в целевой объект.