JavaScriptのObject.assignの使い方!プロパティをコピーする

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

これは、オブジェクトの結合(マージ)、オブジェクトのコピー、デフォルト値の設定など、オブジェクトの操作において非常に頻繁に利用される便利なメソッドです。
特に、オブジェクトのプロパティを効率的に統合したい場合に役立ちます。

この記事では、Object.assignメソッドの基本的な使い方、その動作原理、引数、戻り値、主要な使用例、そして使用する際の重要な注意点について解説します。

Object.assignメソッドの基本的な構文

Object.assign() メソッドの基本的な構文は以下の通りです。

Object.assign(target, ...sources)
  • target: ターゲットオブジェクトです。ソースオブジェクトのプロパティがコピーされる先のオブジェクトであり、このオブジェクトが変更され、戻り値として返されます
  • ...sources: 1つ以上のソースオブジェクトです。これらのオブジェクトの列挙可能な自身のプロパティがtargetにコピーされます。

target(ターゲットオブジェクト)

  • 必須です。
  • このオブジェクトが変更され、結果として返されます。
  • targetがプリミティブ値(文字列、数値、真偽値、nullundefined、シンボル、BigInt)の場合、内部的にオブジェクトに変換されてからプロパティがコピーされます。ただし、これは厳格モード ("use strict") ではエラーになる可能性があります。通常はオブジェクトをターゲットとして使用します。

…sources(ソースオブジェクト)

  • オプションで、0個以上のオブジェクトを指定できます。
  • これらのオブジェクトからプロパティがコピーされます。
  • ソースがnullまたはundefinedの場合、そのソースはスキップされ、エラーにはなりません。
  • ソースがプリミティブ値の場合、オブジェクトに変換されますが、列挙可能な自身のプロパティ(例えば"a"という文字列のlengthプロパティなど)は通常存在しないため、ほとんど影響はありません。

Object.assignの戻り値

変更されたターゲットオブジェクトが返されます。

Object.assignメソッドを使ってみる

実際にObject.assignを使ってみます。

例1:基本的なオブジェクトのコピー

既存のオブジェクトのプロパティを新しいオブジェクトにコピーします。

const obj1 = { a: 1, b: 2 };
const obj2 = {}; // コピー先の新しい空オブジェクト

// obj1のプロパティをobj2にコピー
Object.assign(obj2, obj1);

console.log(obj2); // 出力: { a: 1, b: 2 }
console.log(obj1 === obj2); // 出力: false (異なるオブジェクト参照)

この例では、obj1のプロパティが空のobj2にコピーされ、obj2{ a: 1, b: 2 }になります。
obj1obj2は異なるオブジェクト参照を持つため、独立しています。

例2:複数のオブジェクトを結合する(マージ)

複数のソースオブジェクトのプロパティを1つのターゲットオブジェクトに結合します。

const objA = { x: 1, y: 2 };
const objB = { y: 3, z: 4 }; // yが重複
const objC = { w: 5 };

const mergedObj = {};

// objA, objB, objCのプロパティをmergedObjに結合
Object.assign(mergedObj, objA, objB, objC);

console.log(mergedObj); // 出力: { x: 1, y: 3, z: 4, w: 5 }

objBy: 3objAy: 2を上書きしていることに注目してください。
このように後から指定されたソースのプロパティが優先されます。

例3:オブジェクトのクローン(浅いコピー)

元のオブジェクトを変更せずにコピーを作成したい場合によく使われます。

const original = {
    name: 'Product A',
    details: {
        price: 100,
        currency: 'USD'
    },
    tags: ['electronics', 'gadget']
};

// 空のオブジェクトをターゲットにしてオリジナルをコピー
const clone = Object.assign({}, original);

console.log(clone);
// 出力: { name: 'Product A', details: { price: 100, currency: 'USD' }, tags: ['electronics', 'gadget'] }

// シャローコピーの挙動を確認
clone.name = 'Product B'; // プリミティブ値は独立
clone.details.price = 120; // ネストされたオブジェクト(ここでは `details`)はメモリ上で同じオブジェクトを指しているため、
                           // 片方を変更するともう片方にも影響が出ます(= シャローコピーの特性)

clone.tags.push('new'); // 配列への参照がコピーされているため、元の配列も変更される

console.log(original.name); // 出力: Product A (変更なし)
console.log(original.details.price); // 出力: 120 (変更された!)
console.log(original.tags); // 出力: [ 'electronics', 'gadget', 'new' ] (変更された!)

この例は、Object.assignがシャローコピーであることを明確に示しています。

detailsオブジェクトやtags配列のようなネストされたオブジェクトは、参照がコピーされます。
そのためクローン側で変更すると元のオブジェクトにも影響します。

一方nameプロパティに関しては、独立しているので、クローンとオリジナルで別々の値が保持されます。

例4:デフォルト値の設定(ユーザー設定を優先)

オブジェクトに特定のプロパティが存在しない場合にデフォルト値を適用しつつ、ユーザーが設定済みのプロパティはそのまま維持したい場合に便利です。

const userSettings = {
    theme: 'dark',
    fontSize: 14 // fontSizeはユーザーが設定済み
};

const defaultSettings = {
    theme: 'light', // デフォルトのテーマ
    fontSize: 16,   // デフォルトのフォントサイズ
    notifications: true, // デフォルトで通知をオン
    language: 'en'       // デフォルト言語
};

// 新しい空のオブジェクトをターゲットにし、
// まずdefaultSettingsをコピーし、次にuserSettingsをコピーします。
// これにより、userSettingsのプロパティがdefaultSettingsのプロパティを上書きし、
// userSettingsにないプロパティはdefaultSettingsから補完されます。
const finalSettings = Object.assign({}, defaultSettings, userSettings);

console.log(finalSettings);
// 出力: { theme: 'dark', fontSize: 14, notifications: true, language: 'en' }
// 説明: themeとfontSizeはuserSettingsの値が優先され、
//       notificationsとlanguageはdefaultSettingsから追加されました。

この例では、Object.assign({}, defaultSettings, userSettings)という順序が重要です。

まず空のオブジェクト{}が作成され、それがターゲットとなります。
次にdefaultSettingsのプロパティがこのターゲットにコピーされます。

最後にuserSettingsのプロパティがコピーされます。
このとき、themefontSizeuserSettingsの値で上書きされるため、ユーザーの設定が優先されます。
notificationslanguageuserSettingsに存在しないため、先にコピーされているdefaultSettingsの値がそのまま残ります。

これにより、「ユーザー設定を優先し、足りない部分をデフォルト値で補完する」という意図通りの結果が得られます。

Object.assignを使う際の重要な注意点

シャローコピーであること

最も重要な注意点です。
ネストされたオブジェクトや配列は、参照渡しでコピーされます。
深いコピー(ディープコピー)が必要な場合は、Object.assignではなく、JSONのシリアライズ/デシリアライズ (JSON.parse(JSON.stringify(obj))) や、Lodashの_.cloneDeep()のようなライブラリ、または再帰的なカスタム関数を使用する必要があります。

ターゲットオブジェクトが変更される(破壊的)

Object.assignは、最初の引数であるターゲットオブジェクトを直接変更します。
元のオブジェクトを保持したい場合は、空のオブジェクトをターゲットとして使用し、そこにコピーするようにしてください(例: Object.assign({}, source)

列挙可能な自身のプロパティのみをコピー

Object.definePropertyenumerable: falseと設定されたプロパティはコピーされません。
また、プロトタイプチェーン上の継承されたプロパティはコピーされません。

ゲッターとセッターの挙動

ソースオブジェクトにゲッター(getter)プロパティがある場合、そのゲッターが評価され、その戻り値がターゲットオブジェクトにコピーされます。
セッター(setter)は呼び出されません。

プリミティブ値のターゲット

ターゲットがプリミティブ値の場合、内部的にオブジェクトに変換されますが、厳格モードではエラーになる可能性があります。
常にオブジェクトをターゲットとして使用するのが安全です。

nullまたはundefinedのソース

ソースオブジェクトがnullまたはundefinedの場合、それらはスキップされ、エラーにはなりません。

まとめ

JavaScriptのObject.assignメソッドは、オブジェクトのプロパティを結合したりコピーしたりするための強力で簡潔なツールです。
複数のオブジェクトをマージしたり、オブジェクトの浅いコピーを作成したり、デフォルト値を設定したりする際に非常に便利です。

しかし、シャローコピーであること、そしてターゲットオブジェクトが変更される(破壊的)という重要な特性を理解して使用することが不可欠です。
これらの注意点を踏まえ、Object.assignを効果的に活用し、JavaScriptアプリケーションにおけるオブジェクト操作を効率化しましょう。

コメント