Object.assign()

Object.assign() メソッドは、すべての列挙可能自身のプロパティの値を、1つ以上のコピー元オブジェクトからコピー先オブジェクトにコピーするために使用されます。

構文

Object.assign(target, ...sources)

引数

target
コピー先オブジェクト — ソースのプロパティを適用するもので、変更後に返されます。
sources
コピー元オブジェクト — 適用したいプロパティを含むオブジェクトです。

返値

コピー先オブジェクトです。

解説

コピー先オブジェクトのプロパティは、コピー元に同じキーのプロパティがあると上書きされます。より後のコピー元のプロパティが、より前のものを同様に上書きします。

Object.assign() メソッドは、コピー元オブジェクトから列挙可能 (enumerable) かつ直接所有 (own) のプロパティだけをコピー先オブジェクトにコピーします。この際、コピー元オブジェクトには [[Get]]、コピー先オブジェクトには [[Set]] を使いますので、ゲッターセッターを呼び出すことになります。これはプロパティの代入 (assign) であり、プロパティをコピーしたり新しく定義をしたりするのとは違います。そのため、コピー元にゲッターが存在する場合、新しいプロパティをプロトタイプにマージする用途には不適切でしょう。

プロパティ定義を (列挙可能 (enumerable) 属性も含めて) プロトタイプの中にコピーするには、このメソッドではなく Object.getOwnPropertyDescriptor()Object.defineProperty() を使用してください。

StringSymbol の両方のプロパティがコピーされます。

エラーが発生した場合、例えばプロパティが書き込み不可の場合は、 TypeError が発生しますが、エラーが発生する前にプロパティが追加される場合、 target オブジェクトが変更されることがあります。

Note: Object.assign() はコピー元の値が nullundefined でも例外を投げないことに注意して下さい。

ポリフィル

このポリフィルは、 ES5 にシンボルがないため、シンボルのプロパティに対応していません。

if (typeof Object.assign !== 'function') {
  // Must be writable: true, enumerable: false, configurable: true
  Object.defineProperty(Object, "assign", {
    value: function assign(target, varArgs) { // .length of function is 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) {
            // Avoid bugs when hasOwnProperty is shadowed
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}

オブジェクトの複製

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}}
  
  // Deep Clone
  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 } (cf. bug 1207182 on Firefox)
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 = 'abc';
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": "a", "1": "b", "2": "c" }

例外が発生すると実行中のコピー作業が中断される

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, 三番目のコピー元もコピーされない

アクセサーのコピー

var 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;
    }, {});
    
    // By default, Object.assign copies enumerable Symbols, too
    Object.getOwnPropertySymbols(source).forEach(sym => {
      let 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 } }

仕様書

仕様書
ECMAScript (ECMA-262)
Object.assign の定義

ブラウザーの互換性

Update compatibility data on GitHub
デスクトップモバイルサーバー
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewAndroid 版 ChromeAndroid 版 FirefoxAndroid 版 OperaiOSのSafariSamsung InternetNode.js
assignChrome 完全対応 45Edge 完全対応 12Firefox 完全対応 34IE 未対応 なしOpera 完全対応 32Safari 完全対応 9WebView Android 完全対応 45Chrome Android 完全対応 45Firefox Android 完全対応 34Opera Android 完全対応 32Safari iOS 完全対応 9Samsung Internet Android 完全対応 5.0nodejs 完全対応 4.0.0

凡例

完全対応  
完全対応
未対応  
未対応

関連情報