[JavaScript]Object.assignは子プロパティがオブジェクトだった場合は上書きしてしまう

今回Node.jsでハマったところを書きます。

まず、Object.assign の使い方。以下のような関係にあるとき、うまく両方を組み合わせてくれます。

const a = { a: 1 }
const b = { b: 2 }
console.log(Object.assign({}, a, b))

この結果、「{ a: 1, b: 2 }」を出してくれます。Object.assign の最初の引数に空のオブジェクトを入れておくことで、元のオブジェクトに影響を与えないようにしています。

次に、各オブジェクトのプロパティを結合したい場合、以下でうまくやってくれそうな気がしたのですが。

const a = { child: { a: 1 }}
const b = { child: { b: 1 }}
console.log(Object.assign({}, a, b))

結果は「{ child: { b: 1 } }」つまり後に指定したものでchildが上書きされます。assignは直接の子要素までを面倒をみてくれるようなので、今回については以下のような対応を行うのがよさそうです。

const a = { child: { a: 1 }}
const b = { child: { b: 1 }}
console.log(Object.assign({}, a, b, { child: Object.assign(a.child, b.child) }))

「{ child: { a: 1, b: 1 } }」後ろに指定したものが優先ということで、Object.assignを重ねて使い、childを最後に持って行きました。直属だけではなく末代まで処理する場合はforと関数のネストが現実的だと思います。下記はざっと作りましたが、配列にオブジェクトがある場合の考慮が別途必要です。

const a = { a: 1 };
const b = { b: 1 };
const c = function (a, b) {
    var r = Object.assign({}, a);
    for (var i in b) {
        const v = b[i];
        if (v instanceof Array) { r[i] = Array.concat([], v) }
        else if (v instanceof Object) { r[i] = arguments.callee(r[i], v) }
        else { r[i] = v }
    }
    return r;
};
console.log(c(a, b))