Object.assign()

Метод Object.assign() призначено для копіювання у цільовий об'єкт всіх особистих (не успадкованих) перелічуваних властивостей одного або декількох об'єктів. Метод повертає цільовий об'єкт.

Синтаксис

Object.assign(target, ...sources)

Параметри

target
Цільовий об'єкт.
sources
Вхідні об'єкти (щонайменше один).

Вертає

Цільовий об'єкт.

Опис

Властивості цільового об'єкта будуть заміщені властивостями вхідних об'єктів, якщо вони мають той самий ключ. Пізніші властивості вхідних об'єктів схожим чином заміщують більш ранні.

Метод Object.assign() копіює лише перелічувані та особисті властивості з кожного джерела у цільовий об'єкт. Він використовує [[Get]] на вхідних об'єктах та [[Set]] на цільовому об'єкті, а отже, здійснює виклики гетерів і сетерів. Таким чином, він присвоює властивості, а не просто копіює чи визначає нові властивості. Ця особливість може зробити метод непридатним для приєднання нових властивостей до прототипу, якщо вхідні джерела містять гетери. Для копіювання визначень властивостей разом з їх перелічуваністю у прототип натомість скористайтеся методами Object.getOwnPropertyDescriptor() та Object.defineProperty().

Копіюються властивості обох типів: як рядкові, так і символьні.

В разі помилки, наприклад, якщо властивість є недоступною для запису, викидається помилка TypeError, а об'єкт target може бути змінений, якщо будь-які властивості були додані до викидання помилки.

Заувага: Object.assign() не викидає помилок, якщо серед джерел є значення null чи undefined.

Приклади

Клонування об'єкта

var obj = { a: 1 };
var copy = Object.assign({}, obj);
console.log(copy);  // { a: 1 }

Заувага щодо створення глибокої копії

Для створення глибокої копії необхідно скористатись альтернативними методами, тому що метод Object.assign() копіює значення властивостей. Якщо значенням джерела буде посилання на об'єкт, метод скопіює лише значення посилання.

function test() {
  'use strict';

  let obj1 = { a: 0 , b: { c: 0}};
  let obj2 = Object.assign({}, obj1);
  console.log(JSON.stringify(obj2));  // { a: 0, b: { c: 0}}
  
  obj1.a = 1;
  console.log(JSON.stringify(obj1));  // { a: 1, b: { c: 0}}
  console.log(JSON.stringify(obj2));  // { a: 0, b: { c: 0}}
  
  obj2.a = 2;
  console.log(JSON.stringify(obj1));  // { a: 1, b: { c: 0}}
  console.log(JSON.stringify(obj2));  // { a: 2, b: { c: 0}}
  
  obj2.b.c = 3;
  console.log(JSON.stringify(obj1));  // { a: 1, b: { c: 3}}
  console.log(JSON.stringify(obj2));  // { a: 2, b: { c: 3}}
  
  // Глибока копія
  obj1 = { a: 0 , b: { c: 0}};
  let obj3 = JSON.parse(JSON.stringify(obj1));
  obj1.a = 4;
  obj1.b.c = 4;
  console.log(JSON.stringify(obj3));  // { a: 0, b: { c: 0}}
}

test();

Злиття об'єктів

var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };

var obj = Object.assign(o1, o2, o3);

console.log(obj);  // { a: 1, b: 2, c: 3 }
console.log(o1);   // { a: 1, b: 2, c: 3 }, змінено сам цільовий об'єкт

Злиття об'єктів з однаковими властивостями

var o1 = { a: 1, b: 1, c: 1 };
var o2 = { b: 2, c: 2 };
var o3 = { c: 3 };

var obj = Object.assign({}, o1, o2, o3);

console.log(obj);  // { a: 1, b: 2, c: 3 }

Властивості перезаписуються однойменними властивостями інших об'єктів, що стоять далі в списку параметрів.

Копіювання символьних властивостей

var o1 = { a: 1 };
var o2 = { [Symbol('foo')]: 2 };

var obj = Object.assign({}, o1, o2);

// Через ваду 1207182 у Firefox друга властивість (з ключем symbol) не виводиться
console.log(obj);  // { a : 1, [Symbol("foo")]: 2 }

Object.getOwnPropertySymbols(obj);  // [Symbol(foo)]

Властивості з ланцюжка прототипів та неперелічувані властивості скопіювати не можна

var obj = Object.create({ foo: 1 }, { // властивість foo належить до ланцюжка прототипів об'єкта obj
  bar: {
    value: 2  // bar є неперелічуваною властивістю
  },
  baz: {
    value: 3,
    enumerable: true  // baz є перелічуваною особистою властивістю
  }
});

var copy = Object.assign({}, obj);
console.log(copy);  // { baz: 3 }

Прості величини буде загорнуто у об'єкти

var v1 = 'абв';
var v2 = true;
var v3 = 10;
var v4 = Symbol('foo');

var obj = Object.assign({}, v1, null, v2, undefined, v3, v4); 
// Прості величини будуть загорнуті, null та undefined ігноруються.
// Зауважте, лише рядкові обгортки можуть мати особисті неперелічувані властивості.
console.log(obj); // { "0": "а", "1": "б", "2": "в" }

Винятки переривають процес копіювання

var target = Object.defineProperty({}, 'foo', {
  value: 1,
  writable: false
}); // Властивість target.foo доступна лише для читання

Object.assign(target, { bar: 2 }, { foo2: 3, foo: 3, foo3: 3 }, { baz: 4 });
// TypeError: "foo" is read-only 
// Викидається виняток під час присвоювання target.foo

console.log(target.bar);   // 2, перше джерело було успішно скопійовано
console.log(target.foo2);  // 3, першу властивість другого джерела було успішно скопійовано
console.log(target.foo);   // 1, тут трапилась помилка
console.log(target.foo3);  // undefined, метод assign завершився, властивість foo3 не буде скопійована
console.log(target.baz);   // undefined, третє джерело також не буде скопійоване

Копіювання аксесорів

var obj = {
  foo: 1,
  get bar() {
    return 2;
  }
};

var copy = Object.assign({}, obj);
console.log(copy);
// { foo: 1, bar: 2 }, значенням copy.bar буде значення, що поверне геттер obj.bar

// Ця функція присвоювання повністю скопіює дескриптори
function completeAssign(target, ...sources) {
  sources.forEach(source => {
    let descriptors = Object.keys(source).reduce((descriptors, key) => {
      descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
      return descriptors;
    }, {});
    // За замовчуванням, метод Object.assign копіює перелічувані властивості типу Symbol також
    Object.getOwnPropertySymbols(source).forEach(sym => {
      let descriptor = Object.getOwnPropertyDescriptor(source, sym);
      if (descriptor.enumerable) {
        descriptors[sym] = descriptor;
      }
    });
    Object.defineProperties(target, descriptors);
  });
  return target;
}

var copy = completeAssign({}, obj);
console.log(copy);  
// { foo:1, get bar() { return 2 } }

Запасний варіант (поліфіл)

Наведений поліфіл не підтримує символьних властивостей, позаяк ES5 не має такого типу:

if (typeof Object.assign != 'function') {
  // Має бути writable: true, enumerable: false, configurable: true
  Object.defineProperty(Object, "assign", {
    value: function assign(target, varArgs) { // .length функції дорівнює 2
      'use strict';
      if (target == null || target === undefined) {
        throw new TypeError('Cannot convert undefined or null to object');
      }

      var to = Object(target);

      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource != null && nextSource !== undefined) {
          for (var nextKey in nextSource) {
            // Запобігає помилок, коли hasOwnProperty заміщено
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
     },
     writable: true,
     configurable: true
   });
 }

Специфікації

Специфікація Статус Коментар
ECMAScript 2015 (6th Edition, ECMA-262)
The definition of 'Object.assign' in that specification.
Standard Первинне визначення.
ECMAScript Latest Draft (ECMA-262)
The definition of 'Object.assign' in that specification.
Draft

Сумісність з веб-переглядачами

Update compatibility data on GitHub
DesktopMobileServer
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidFirefox for AndroidOpera for AndroidSafari on iOSSamsung InternetNode.js
assignChrome Full support 45Edge Full support 12Firefox Full support 34IE No support NoOpera Full support 32Safari Full support 9WebView Android Full support 45Chrome Android Full support 45Firefox Android Full support 34Opera Android Full support 32Safari iOS Full support YesSamsung Internet Android Full support 5.0nodejs Full support 4.0.0

Legend

Full support  
Full support
No support  
No support

Див. також