JavaScriptを利用してアプリケーションコードを記述していると2つのオブジェクトをマージしたいという場面に頻繁に出くわします。そのような場面においてObject.assignを利用することで簡単にオブジェクトをマージ(まとめる)することができます。

オブジェクトをマージする際に2つのオブジェクトが同じプロパティを持っているとその値を上書きすることができます。またObject.assignはオブジェクトの上書きだけに利用されるものではなくオブジェクトの複製にも利用することができます。Object.assignは入力フォームなどで頻繁に利用され目にする機会も多い機能のためどのような処理を行えるのかを理解しておく必要があります。

本文書ではObject.assignの基本操作とフォームの値の保持の例を使って説明を行なっています。どのように利用しているか例を見ることでオブジェクトのマージをぐっと身近に感じることができる関数です。

オブジェクトをマージする

2つのオブジェクトを用意し、Object.assignでマージするとどのような結果になるか確認します。


const target = { a: 1, b: 2}
const source = { c: 3, d: 4}

const returnedTarget = Object.assign(target, source);

console.log('target:', target);
console.log('source:', source);
console.log('returnedTarget', returnedTarget);

console.log(returnedTarget === target);

実行すると以下の結果となります。


$ node test.js 
target: { a: 1, b: 2, c: 3, d: 4 }
source: { c: 3, d: 4 }
returnedTarget { a: 1, b: 2, c: 3, d: 4 }
true

Object.assignの第一引数のtargetはsourceがマージされていますがsourceの中身は変わりません。またObject.assignの戻り値のreturnedTargetには、マージされたオブジェクトが入ることがわかります。

Object.assignの戻り値はtargetであることがわかります。

オブジェクトをマージする(同じプロパティを持つ場合)

targetとsourceでbという同じプロパティを持つ場合にどのような結果になるか確認します。


const target = { a: 1, b: 2 };
const source = { b: 3, d: 4 };

const returnedTarget = Object.assign(target, source);

console.log('target:', target);
console.log('source:', source);
console.log('returnedTarget', returnedTarget);

console.log(returnedTarget === target);

targetのプロパティbはsourceのプロパティbに上書きされることがわかります。


$ node test.js 
target: { a: 1, b: 3, d: 4 }
source: { b: 3, d: 4 }
returnedTarget { a: 1, b: 3, d: 4 }
true

オブジェクトの配列の要素を更新する

3つのオブジェクトが入った配列を準備します。


const emplyees = [
  {
    firstName: 'John',
    lastName: 'Doe',
  },
  {
    firstName: 'David',
    lastName: 'Hue',
  },
  {
    fistName: 'Jack',
    lastName: 'Daniel',
  },
];

2番目の配列のオブジェクトのlastNameだけ変更したい場合にObject.assignを利用することができます。


const emplyees = [
  {
    firstName: 'John',
    lastName: 'Doe',
  },
  {
    firstName: 'David',
    lastName: 'Hue',
  },
  {
    fistName: 'Jack',
    lastName: 'Daniel',
  },
];

const emplyee = {
  firstName: 'David',
  lastName: 'Cameron',
};

Object.assign(emplyees[1], emplyee);

console.log(emplyees);

実行結果は下記のようになります。


$ node test.js
[
  { firstName: 'John', lastName: 'Doe' },
  { firstName: 'David', lastName: 'Cameron' },
  { fistName: 'Jack', lastName: 'Daniel' }
]

Objectのコピー(クローン)

Object.assignはオブジェクトのコピーにも頻繁に利用されます。第一引数に空のオブジェクトを指定することで全く新しいuser_cloneというオブジェクトを作成することができます。


const user = { firstName: 'John', lastName: 'Doe' };
const user_clone = Object.assign({}, user);

console.log('user:', user);
console.log('user_clone:', user_clone);

console.log(user === user_clone);

userとuser_cloneを比較するとfalseになります。


user: { firstName: 'John', lastName: 'Doe' }
user_clone: { firstName: 'John', lastName: 'Doe' }
false

引数が複数ある場合

ここまではObject.assignの引数がtargetとsourceのみの2つのみでした。Object.assignでは複数の引数をとることができます。


const user = {
  username: 'John',
};

const user_id = {
  id: 1,
};

const email = {
  email: 'john@example.com',
};

returndUser = Object.assign(user, user_id, email);

console.log('user:', user);
console.log('user_id:', user_id);
console.log('email:', email);
console.log('returndUser:', returndUser);

実行すると引数を複数にしてもmergeされていることがわかります。


% node test.js
user: { username: 'John', id: 1, email: 'john@example.com' }
user_id: { id: 1 }
email: { email: 'john@example.com' }
returndUser: { username: 'John', id: 1, email: 'john@example.com' }

4つの引数でも大丈夫なのか確認しておきます。


const user = {
  username: 'John',
};

const user_id = {
  id: 1,
};

const email = {
  email: 'john@example.com',
};

const tel = {
  tel: '090-1111-1111',
};

Object.assign(user, user_id, email, tel);

console.log(user);

//結果
{
  username: 'John',
  id: 1,
  email: 'john@example.com',
  tel: '090-1111-1111'
}

下記のように追加したオブジェクトに同じキーが入っている場合はどのキーが保存されるのか確認しておきます。最後に追加したオブジェクトで上書きされることがわかります。


const user = {
  id: 0,
};

const user_id_1 = {
  id: 1,
};

const user_id_2 = {
  id: 2,
};

const user_id_3 = {
  id: 3,
};

Object.assign(user, user_id_1, user_id_2, user_id_3);

console.log(user);

//結果
{ id: 3 }

Object.assignを使った例(form)

どのような時にObject.assignを使えばいいのかという疑問があると思うので入力フォームの値を保持するformオブジェクトを使って説明したいと思います。

formオブジェクトを準備します。firstNameとlastNameの2つのプロパティを持ちどちらも初期値はnullとします。


const form = {
	firstName: null,
	lastName: null
}

入力フォームに値が入り、inputオブジェクトで受け取ったとします。


const input = {
	firstName: 'John Doe'
}

formオブジェクトをinputオブジェクトで上書きする場合にObject.assignを利用すると下記のように記述することでfirstNameの値を上書きすることができます。


Object.assign(form, input)

結果は下記となります。


{ firstName: 'John Doe', lastName: null }

Object.assignを利用しなくても下記のようにfirstNameの値を変更することは可能です。このシンプルな例であればObject.assignを使う利点が分かりにくいと思います。


form.firstName = input.firstName;

先ほどとはことなりformの要素を少し複雑にした場合にObject.assignを利用するとコードがシンプルになることがわかります。7つにプロパティの内、3つのプロパティを一度に更新したい場合にObject.assignを利用すると下記のように3つの値のみ上書きすることができます。


const form = {
	firstName: null,
	lastName: null,
	Email: null,
	zipCode: null,
	Address: null,
	Phone: null
}

const input = {
	firstName: 'John',
	lastName: 'Doe',
	Email: 'john@example.com'
}

Object.assign(form, input)

console.log(form)

結果は下記のようになります。


 $ node test.js 
{
  firstName: 'John',
  lastName: 'Doe',
  Email: 'john@example.com',
  zipCode: null,
  Address: null,
  Phone: null
}

またフォームで住所に関する値をaddressというオブジェクトで保持した場合もObject.assignを使ってformの値を上書きすることができます。


const address = {
	Address : {
		Prefecture: 'Tokyo',
		address_1: 'Setagaya-ku',
		address_2: 'kyoudou1'
	}
}
Object.assign(form, address);
console.log(form);

結果は下記となります。


 $ node test.js 
{
  firstName: 'John',
  lastName: 'Doe',
  Email: 'john@example.com',
  zipCode: null,
  Address: {
    Prefecture: 'Tokyo',
    address_1: 'Setagaya-ku',
    address_2: 'kyoudou1'
  },
  Phone: null
}

Shallow Copy(浅いコピー)

object.assignを利用したオブジェクトのマージにはShallow Copyという性質があります。Shallowは浅いという意味を持っています。Shallow Copyとはどのようなものか先ほど作成したformオブジェクトを利用して動作確認を行います。


const form = {
  firstName: 'John',
  lastName: 'Doe',
  Email: 'john@example.com',
  zipCode: null,
  Address: {
    Prefecture: 'Tokyo',
    address_1: 'Setagaya-ku',
    address_2: 'kyoudou1',
  },
  Phone: null,
};
const form_clone = Object.assign({}, form);

console.log('form:', form);
console.log('form_clone:', form_clone);

console.log(form === form_clone);
console.log(form.address === form_clone.adress);

上記のコードではobject.assignを利用してformオブジェクトから新たにform_cloneを作成しています。

実行すると以下の結果となります。


% node test.js
form: {
  firstName: 'John',
  lastName: 'Doe',
  Email: 'john@example.com',
  zipCode: null,
  Address: {
    Prefecture: 'Tokyo',
    address_1: 'Setagaya-ku',
    address_2: 'kyoudou1'
  },
  Phone: null
}
form_clone: {
  firstName: 'John',
  lastName: 'Doe',
  Email: 'john@example.com',
  zipCode: null,
  Address: {
    Prefecture: 'Tokyo',
    address_1: 'Setagaya-ku',
    address_2: 'kyoudou1'
  },
  Phone: null
}
false
true

formとform_cloneは別のオブジェクトなので比較ではfalseになっています。しかしネストされたAddressオブジェクトで比較を行うと値はtrueになっています。つまりAddressは同じオブジェクトを参照していることになります。

formオブジェクトをobject.assignを利用してコピーする際にformの直下のプロパティはコピーされネストされたオブジェクトはコピーされず参照されることになります。そのため名前がShallow Copy(浅いコピー)と言われます。

コピーされているfirstNameと参照であるAddress.Prefectureを更新してみてどのような結果になるか確認します。


const form = {
  firstName: 'John',
  lastName: 'Doe',
  Email: 'john@example.com',
  zipCode: null,
  Address: {
    Prefecture: 'Tokyo',
    address_1: 'Setagaya-ku',
    address_2: 'kyoudou1',
  },
  Phone: null,
};
const form_clone = Object.assign({}, form);

form_clone.firstName = 'Jane';
form_clone.Address.Prefecture = 'Osaka';

console.log('form:', form);
console.log('form_clone:', form_clone);

結果firstNameのみはform_cloneのみで更新されていますがAddress.Prefectureはform, form_cloneどちらでも更新が行われています。


% node test.js
form: {
  firstName: 'John',
  lastName: 'Doe',
  Email: 'john@example.com',
  zipCode: null,
  Address: {
    Prefecture: 'Osaka',
    address_1: 'Setagaya-ku',
    address_2: 'kyoudou1'
  },
  Phone: null
}
form_clone: {
  firstName: 'Jane',
  lastName: 'Doe',
  Email: 'john@example.com',
  zipCode: null,
  Address: {
    Prefecture: 'Osaka',
    address_1: 'Setagaya-ku',
    address_2: 'kyoudou1'
  },
  Phone: null
}

スプレット構文は?

オブジェクトのマージであればスプレッド構文を思い浮かべる人も多いかと思います。スプレット構文を利用してオブジェクトのマージを行ってみます。


const target = { a: 1, b: 2 };
const source = { c: 3, d: 4 };

const returnedTarget = { ...target, ...source };

console.log(target);
console.log(source);
console.log(returnedTarget);

console.log(target === returnedTarget);

returnedTargetで2つのオブジェクトがマージされていることがわかります。大きな違いはconsole.log(target)の結果です。object.assginでは元のオブジェクトであるtargetが変更されていましたがスプレット構文では元のオブジェクトが変更されることはありません。


% node test.js
{ a: 1, b: 2 }
{ c: 3, d: 4 }
{ a: 1, b: 2, c: 3, d: 4 }

false

スプレット構文との違いわかりここまでを理解することができたら、Object.assignの基本機能と使い方が理解できたと思います。まだ一度も利用したことがない人はぜひチャレンジしてみてください。