JavaScriptにおけるオブジェクトマージ:スプレッド構文で実現するシャローマージとディープマージ
JavaScriptにおけるスプレッド構文を用いたディープマージとシャローマージの詳細解説
JavaScriptにおいて、オブジェクトや配列を結合する方法はいくつか存在しますが、その中でも汎用性と利便性が高いのが「スプレッド構文」を用いた方法です。しかし、スプレッド構文によるマージには「シャローマージ」と「ディープマージ」の2種類があり、それぞれ異なる挙動を示します。本記事では、この2つのマージの違いと、それぞれの具体的な実装方法について詳しく解説します。
シャローマージとは
シャローマージとは、オブジェクトや配列の1つ上の階層のみを結合する方法です。具体的には、スプレッド構文を用いてソースオブジェクトのプロパティを展開し、ターゲットオブジェクトにコピーします。この際、ソースオブジェクト内のプロパティがオブジェクトや配列の場合、それらは参照渡しとなり、元のオブジェクトと同じオブジェクト・配列が共有されることになります。
例:シャローマージ
const sourceObject = {
prop1: 'value1',
prop2: {
key1: 'value2',
key2: 'value3'
}
};
const targetObject = {};
const mergedObject = { ...targetObject, ...sourceObject };
console.log(mergedObject); // { prop1: 'value1', prop2: { key1: 'value2', key2: 'value3' } }
console.log(mergedObject.prop2 === sourceObject.prop2); // true (同じオブジェクトを参照)
上記の例では、mergedObject.prop2
には sourceObject.prop2
への参照が格納されています。そのため、mergedObject.prop2
を変更すると、sourceObject.prop2
の内容も同様に更新されてしまいます。
ディープマージとは
ディープマージとは、オブジェクトや配列を再帰的に探索し、全ての子孫プロパティを結合する方法です。つまり、シャローマージとは異なり、ネストされたオブジェクトや配列も全て新しいオブジェクト・配列にコピーされます。その結果、元のオブジェクトとは独立した、新しいデータ構造が生成されます。
例:ディープマージ
const sourceObject = {
prop1: 'value1',
prop2: {
key1: 'value2',
key2: 'value3'
}
};
const targetObject = {};
function deepMerge(target, source) {
for (const key of Object.keys(source)) {
if (isObject(source[key])) {
if (!isObject(target[key])) {
target[key] = {};
}
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
const mergedObject = deepMerge(targetObject, sourceObject);
console.log(mergedObject); // { prop1: 'value1', prop2: { key1: 'value2', key2: 'value3' } }
console.log(mergedObject.prop2 === sourceObject.prop2); // false (異なるオブジェクト)
上記の例では、deepMerge
関数を用いてディープマージを実装しています。この関数では、ソースオブジェクトのプロパティを再帰的に処理し、ターゲットオブジェクトにコピーしています。また、ネストされたオブジェクトや配列を検知した場合、再帰的にマージ処理を実行することで、完全なディープマージを実現しています。
シャローマージとディープマージは、それぞれ異なるユースケースに適しています。
- オブジェクトや配列の単純な結合
- 参照渡しによるデータ共有が必要な場合
- パフォーマンスが重要となる場合
- ネストされた構造を持つオブジェクトや配列の結合
- 元のオブジェクトとは独立した新しいデータ構造が必要な場合
- データの不変性を保ちたい場合
まとめ
JavaScriptにおけるスプレッド構文は、オブジェクトや配列をマージする際に非常に便利な機能です。しかし、シャローマージとディープマージの挙動の違いを理解し、それぞれの特性を活かした使い分けを行うことが重要です。
- [JavaScriptでオブジェクトをマージ(結合)
// シャローマージ
const sourceObject = {
prop1: 'value1',
prop2: {
key1: 'value2',
key2: 'value3'
}
};
const targetObject = {};
const mergedObject = { ...targetObject, ...sourceObject };
console.log(mergedObject); // { prop1: 'value1', prop2: { key1: 'value2', key2: 'value3' } }
console.log(mergedObject.prop2 === sourceObject.prop2); // true (同じオブジェクトを参照)
// ディープマージ
const sourceObject = {
prop1: 'value1',
prop2: {
key1: 'value2',
key2: {
nest1: 'value4',
nest2: 'value5'
}
}
};
const targetObject = {};
function deepMerge(target, source) {
for (const key of Object.keys(source)) {
if (isObject(source[key])) {
if (!isObject(target[key])) {
target[key] = {};
}
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
const mergedObject = deepMerge(targetObject, sourceObject);
console.log(mergedObject); // { prop1: 'value1', prop2: { key1: 'value2', key2: { nest1: 'value4', nest2: 'value5' } } }
console.log(mergedObject.prop2 === sourceObject.prop2); // false (異なるオブジェクト)
シャローマージの例では、スプレッド構文を用いて sourceObject
のプロパティを targetObject
に展開しています。この結果、mergedObject
には sourceObject
のプロパティがそのままコピーされています。また、mergedObject.prop2
は sourceObject.prop2
への参照となるため、mergedObject.prop2
を変更すると sourceObject.prop2
の内容も更新されます。
このように、シャローマージとディープマージは、それぞれ異なる挙動を示します。それぞれの特性を理解し、状況に応じて使い分けることが重要です。
JavaScriptにおけるディープマージの実装方法:その他の方法
Object.assign
関数は、ソースオブジェクトのプロパティをターゲットオブジェクトにコピーする際に便利な機能です。この関数を用いてディープマージを実現するには、再帰的に処理を行う必要があります。
const sourceObject = {
prop1: 'value1',
prop2: {
key1: 'value2',
key2: 'value3'
}
};
const targetObject = {};
function deepMerge(target, source) {
for (const key of Object.keys(source)) {
if (isObject(source[key])) {
if (!isObject(target[key])) {
target[key] = {};
}
deepMerge(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
return target;
}
const mergedObject = deepMerge(targetObject, sourceObject);
console.log(mergedObject); // { prop1: 'value1', prop2: { key1: 'value2', key2: 'value3' } }
console.log(mergedObject.prop2 === sourceObject.prop2); // false (異なるオブジェクト)
上記の例では、deepMerge
関数の中で Object.assign
を用いてソースオブジェクトのプロパティをコピーしています。この方法も、再帰的に処理を行うことでディープマージを実現しています。
lodash.merge を用いた方法
lodash
は、JavaScriptにおける様々なユーティリティ関数を提供するライブラリです。このライブラリには、merge
関数というディープマージ専用の関数が用意されています。
const sourceObject = {
prop1: 'value1',
prop2: {
key1: 'value2',
key2: 'value3'
}
};
const targetObject = {};
const mergedObject = _.merge(targetObject, sourceObject);
console.log(mergedObject); // { prop1: 'value1', prop2: { key1: 'value2', key2: 'value3' } }
console.log(mergedObject.prop2 === sourceObject.prop2); // false (異なるオブジェクト)
上記の例では、lodash
の merge
関数を用いてディープマージを行っています。この関数は、引数として複数のオブジェクトを渡すことで、それらを結合することができます。
自作関数による方法
上記の2つの方法以外にも、自作関数を用いてディープマージを実現することも可能です。この方法は、より柔軟なカスタマイズが可能ですが、実装コストが高くなるというデメリットもあります。
function deepMerge(target, source) {
for (const key of Object.keys(source)) {
if (isObject(source[key])) {
if (!isObject(target[key])) {
target[key] = {};
}
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
上記の例は、自作関数によるディープマージの実装例です。この関数は、再帰的に処理を行うことで、ネストされたオブジェクトや配列も全て新しいオブジェクト・配列にコピーします。
それぞれの方法の比較
方法 | メリット | デメリット |
---|---|---|
スプレッド構文 | シンプルで分かりやすい | ネストが深い場合、コードが冗長になる可能性がある |
Object.assign | 比較的シンプル | 再帰処理が必要 |
lodash.merge | 簡単で使いやすい | ライブラリの導入が必要 |
自作関数 | 柔軟性が高い | 実装コストが高い |
JavaScriptにおけるディープマージには、様々な方法が存在します。それぞれの特徴を理解し、状況に応じて最適な方法を選択することが重要です。
- [How to deep merge arrays and objects with JavaScript - Go Make
javascript spread-syntax