6時だョ!!全員集合!!

Rails・JavaScrictを中心にアウトプットします。

2021年11月12日 JavaScript (JS Primer) オブジェクトのマージと複製

オブジェクトのマージと複製

Object.assignメソッド

Object.assignメソッドを使うと、オブジェクトの複製やオブジェクト同士のマージができます。以下に、Object.assignメソッドの基本構文を記載します。

// 第一引数に、マージさせるtargetオブジェクトを書く。
// sourcesオブジェクトの全ての列挙可能なプロパティを、
// targetオブジェクトにコピーする。
// Object.assignメソッドの戻り値はtargetオブジェクトです。
const obj = Object.assign(target, ...sources);

オブジェクトのマージ

以下のコードでは空のオブジェクトにobjectAとobjectBをマージしています。

const objectA = { a: "a" };
const objectB = { b: "b" };
const merged = Object.assign({}, objectA, objectB);
console.log(merged); // => { a: "a", b: "b" }
  • 第1引数の空オブジェクトに対してコピーし、それを返り値とします。
  • 第2引数以降に指定したオブジェクトの中身が含まれたものが返り値となります。

空のオブジェクトを使わない場合のマージ

以下のコードではobjectAにobjectBを追加しています。

const objectA = { a: "a" };
const objectB = { b: "b" };
const merged = Object.assign(objectA, objectB);
console.log(merged); // => { a: "a", b: "b" }
// `objectA`が変更されている
console.log(objectA); // => { a: "a", b: "b" }
console.log(merged === objectA); // => true
  • この場合objectAの中身は上書きされることとなるため、Object.assignメソッドの第一引数には、空のオブジェクトリテラルを指定するのが典型的な利用方法です。

プロパティ名の上書き

以下のコードでは同じプロパティ名"version"をマージしています。

// `version`のプロパティ名が被っている
const objectA = { version: "a" };
const objectB = { version: "b" };
const merged = Object.assign({}, objectA, objectB);
// 後ろにある`objectB`のプロパティで上書きされる
console.log(merged); // => { version: "b" }
  • オブジェクトのプロパティ名が重複した場合は後に指定した方に上書きされます。
  • JavaScriptでは、基本的に処理は先頭から後ろへと順番に行います。 そのため、空のオブジェクトへobjectAを代入してから、その結果にobjectBを代入するという形になります。

スプレッド構文

オブジェクトの複製とマージはスプレッド構文を用いてもObject.assignと同じ結果が期待出来ます。

// `version`のプロパティ名が被っている
const objectA = { version: "a" };
const objectB = { version: "b" };
const merged = {
    ...objectA,
    ...objectB,
    other: "other"
};
// 後ろにある`objectB`のプロパティで上書きされる
console.log(merged); // => { version: "b", other: "other" }

Object.assignとは異なる点として、必ず新しいオブジェクトを作成します。
spread構文はオブジェクトリテラルの中でのみ記述でき、オブジェクトリテラルは新しいオブジェクトを作成するためです。

変数の代入について

変数の代入は箱に値を入れるイメージとよく言われますが、厳密にいうと、PC上のメモリに値が保持されていて、変数がその値を参照しているイメージです。つまり、=は変数に値を代入というより、変数と参照先の値を紐づける役割です。

// メモリに5が保持されている。
// xはメモリに保持されている5を参照している。
const x = 5;

身近な例で言うと、変数が「伝票」で、値が「物」というイメージです。

オブジェクトの複製の問題点

Object.assignとスプレッド構文を使って、オブジェクトを複製できます。しかし、これらを使ったオブジェクトの複製はshallow copy(浅いコピー)です。オブジェクトのプロパティが参照しているオブジェクトはコピーしません。

const shallowClone = (obj) => {
    return {...obj};
};
const obj = {
    level: 1,
    nest: {
        level: 2
    },
};
const cloneObj = shallowClone(obj);
// nestプロパティのバリューはオブジェクトです。
// shallow copyは、nestプロパティのオブジェクトをコピーしない。
// そのため、nestプロパティは同じオブジェクトを参照する。
console.log(cloneObj.nest) // => { level: 2 }
console.log(obj.nest) // => { level: 2 }

// shallow copyはnestのオブジェクトまでちゃんとコピーしなく、
// 同じものを参照しているから厳密等価演算子がtrueになる。
// コピーしたnestプロパティのオブジェクトは同じオブジェクトのままになります。
console.log(cloneObj.nest === obj.nest); // => true

完全な複製ではないので、同じオブジェクトを参照していることがわかります。 深いコピーを実装するためにはより複雑な処理を書く必要があります。

JavaScriptのビルトインメソッドは浅い(shallow)実装のみを提供し、深い(deep)実装は提供していないことが多いです。 言語としては最低限の機能を提供し、より複雑な機能はユーザー側で実装するという形式を取るためです。

「あさいん」の複製は「あさいんです」

Object.assignは浅いコピーです。

参考

JavaScript Primer オブジェクトのマージと複製