JavaScript Primer 第一部:基本文法「オブジェクト」の#オブジェクトの複製の*深いコピー(deep copy)*が何をしているのか理解できませんでした。学習の過程においては、そんなにこだわるところではなかったかもしれませんが、理解できなくて気持ち悪かったので整理。
以下、JS Primerの解説を引用しておきます。
逆にプロパティの値までも再帰的に複製してコピーすることを、深いコピー(deep copy)と呼びます。 deep copyは、再帰的にshallow copyすることで実現できます。 次のコードでは、deepCloneをshallowCloneを使うことで実現しています。
このように、JavaScriptのビルトインメソッドは浅い(shallow)実装のみを提供し、深い(deep)実装は提供していないことが多いです。 言語としては最低限の機能を提供し、より複雑な機能はユーザー側で実装するという形式を取るためです。
// 引数の`obj`を浅く複製したオブジェクトを返す
const shallowClone = (obj) => { //(5)
return Object.assign({}, obj);
};
// (3)
// 引数の`obj`を深く複製したオブジェクトを返す
function deepClone(obj) {
//(4)
const newObj = shallowClone(obj); // => {level: 1, nest: {level: 2}}
// プロパティがオブジェクト型であるなら、再帰的に複製する
// (6)
Object.keys(newObj) //引数内のオブジェクトのキー(左側)を一つずつ抜き出してくる(forEachに似てる?)
.filter(k => typeof newObj[k] === "object") // (7)
.forEach(k => newObj[k] = deepClone(newObj[k])); // (8)
return newObj;
}
//(1)
const obj = {
level: 1,
nest: {
level: 2
}
};
//(2)
const cloneObj = deepClone(obj);
// (9)`nest`オブジェクトも再帰的に複製されている
console.log(cloneObj.nest === obj.nest); // => false
以下、実際のプログラムの動きとは違うと思いますが、入門者の自分が理解しやすいように流れを整理してみます。
- obj変数にオブジェクトが代入される
- cloneObj変数に、1のobj変数を引数に渡すdeepClone関数が代入すると宣言される。
- 2.で呼ばれたdeepClone関数を実行していく。
- shallowClone関数式を呼び、引数にobjを渡し、newObjに代入する。(関数式はその変数名で参照できる)
- shallowClone関数式の無名関数を実行する。Object.assignメソッドで第一引数のからオブジェクト
{}
にobjを合体させ(クローンする)、それを返す。 - Object.keysで仮引数内のオブジェクト(newObj)のキーを1つ1つ展開する。
-
Array.filterで引数に与えられた関数でnewObjのキーを一つ一つ検査し、該当するプロパティだけで新しい配列を作り、返す。(→if文みたい!)検査内容は、そのキーのデータ型が"object"かどうか(「は?」と思った入門者は下図1参照)。つまり
{level: 2}
だけの配列ができる。 - それに対してforEachを仕掛けて、一つ一つのプロパティに引数内の無名関数を実行する。deepClone関数(自分)を実行し(自分を呼ぶから再帰的と呼ぶ)、newObjオブジェクトのキー
k
のプロパティにその結果を代入する。x-yの処理が{level: 2}
に対して再度行われる。newObjを返す。 - オリジナルのobjのキー
nest
とクローンしたcloneObjのキーnest
を比較し、falseとなるので、複製されたオブジェクトとわかる。
以上です。
てか、コピーと謳うのであれば、プログラムのコードでもCloneではなく、Copyとしてくれればよかったのに、と思うのは私だけでしょうか。