Object.assign()

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

Синтаксис

Object.assign(target, ...sources)

Параметри

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

Вертає

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

Опис

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

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

Для копіювання визначень властивостей разом з їх перелічуваністю у прототип натомість скористайтеся методами Object.getOwnPropertyDescriptor() та Object.defineProperty().

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

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

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

Приклади

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

const obj = { a: 1 };
const 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();

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

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

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

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

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

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);  // { a: 1, b: 2, c: 3 }

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

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

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

const obj = Object.assign({}, o1, o2);
console.log(obj);  // { a : 1, [Symbol("foo")]: 2 }
Object.getOwnPropertySymbols(obj);  // [Symbol(foo)]

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

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

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

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

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

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

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

const 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, третє джерело також не буде скопійоване

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

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

let 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
   });
 }

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

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

BCD tables only load in the browser

Див. також