[JavaScript]シャローコピーとディープコピーを理解する
はじめに
Safari15.4が出たことで全てのモダンブラウザでstructuredClone()
を使いディープコピーができるようになりました。
社内でも話題に上がりましたが、そもそもディープコピーってなんだ・・・?となったので簡単に調べてまとめます。
またシャローコピーについても取り上げます。
JavaScriptのデータ型
シャローコピー・ディープコピーの話の前にまずJavaScriptのデータについての話を少しします。
JavaScriptはプリミティブ型とオブジェクト型の2つに分けられます。
プリミティブ型
真偽値や数値など基本的な値の型のことです。
この型は一度作成すると値自体の変更をできない、イミュータブルの特性があります。
詳細はこちら↓
プリミティブ型のコピー
プリミティブ型のコピーは作成されるのと同時にメモリ上に新しく値を割り当てます。
それぞれ別のメモリに値が存在するのでaをbに代入した後、aの値を変更してもbは変わりません。
プリミティブ型は値として複製される。
let a = 100;
let b = a;
a = 200;
console.log(a); // 200
console.log(b); // 100
オブジェクト
プリミティブ型ではないオブジェクトや配列などのことをオブジェクトと呼びます。
シャローコピー、ディープコピーが必要になるのはオブジェクトや配列などのデータだけ
オブジェクトのコピー
オブジェクトのコピーはプリミティブ型と違い新しく値を作成しない参照渡しです。
変数person
とnewPerson
はメモリ上の同じデータを見にいきます。同じデータに別名を付けていると理解しました。
const person = {name: "Tom"};
const newPerson = user;
person.name = "Bob";
console.log(person); // {name: "Bob"}
console.log(newPerson); // {name: "Bob"}
参照するデータが同じなので値を変更するとそれぞれ値が変わります。
ただ実際はオブジェクトの値が変わると困る場面が多いと思います。
そこでシャローコピー、ディープコピーが必要になります。
シャローコピー、ディープコピー
コピー元とコピー先の参照の深さに応じた呼び名のこと。
シャローコピー
シャローコピー(shallow copy)名前の通り浅いコピーでコピー元の1階層のみ複製します。
メモリ上にオブジェクトの1階層を複製しネストされたオブジェクトは参照を渡します。
シャローコピーはObject.assign
やスプレッド構文で行います。
const person = {
firstname: "Alex",
lastname: "Turner",
birth: {
year: 1986,
month: 1,
date: 6
}
}
const newPerson = { ...person };
newPerson.firstname = "Miles";
newPerson.lastname = "Kane";
console.log(person); // {firstname: "Alex", lastname: "Turner", birth{...}}
console.log(newPerson); // {firstname: "Miles", lastname: "Kane", birth{...}}
1階層目はシャローコピーによって複製されますが、2階層目のオブジェクトはコピー元、コピー先で同じメモリを参照するので片方の値を変えるともう片方も変わってしまいます。
const person = {
// 省略
}
const newPerson = { ...person };
newPerson.firstname = "Miles";
newPerson.lastname = "Kane";
newPerson.birth.month = 3;
newPerson.birth.date = 17;
console.log(person);
// {firstname: 省略, birth: {year: 1986, month: 3, date: 17}}
console.log(newPerson);
// {firstname: 省略, birth: {year: 1986, month: 3, date: 17}}
ディープコピー
ディープコピーは参照ではなく値をコピーします。
メモリに格納されているデータを全てコピーするということです。
ディープコピーしてみる
1. JSON.parse(jSON.stringify(obj))
ググってよく出てくる方法です。
const person = {
firstname: "Alex",
lastname: "Turner",
birth: {
year: 1986,
month: 1,
date: 6
}
}
const newPerson = JSON.parse(JSON.stringify(person));
newPerson.birth.month = 3;
newPerson.birth.date = 17;
console.log(person);
// {..., birth: {year: 1986, month: 1, date: 6}}
console.log(newPerson);
// {..., birth: {year: 1986, month: 3, date: 17}}
問題が発生する場合があるので注意が必要です。
2. lodash
のcloneDeep
2つ目はlodashを使うことです。
事前にインストールする必要がありますが、簡単に使うことができます。
import _ from "lodash";
const person = {
// 省略
};
const newPerson = _.cloneDeep(person);
newPerson.birth.month = 3;
newPerson.birth.date = 17;
console.log(person);
// {..., birth: {year: 1986, month: 1, date: 6}}
console.log(newPerson);
// {..., birth: {year: 1986, month: 3, date: 17}}
3. structuredClone()
3つ目は今回シャローコピー、ディープコピーについて調べるきっかけになった関数です。
下記に現在コピーできる型が書いてあります。関数はできないようです。
const person = {
// 省略
};
const newPerson = structuredClone(person);
newPerson.birth.month = 3;
newPerson.birth.date = 17;
console.log(person);
// {..., birth: {year: 1986, month: 1, date: 6}}
console.log(newPerson);
// {..., birth: {year: 1986, month: 3, date: 17}}
まとめ
シャローコピー、ディープコピーについてまとめました。
実務ではシャローコピーを使うことが多いのですが、今回調べた内容を知っておかないとハマってしまいそうだなと思いました。
-
シャローコピー
オブジェクトの1階層目をコピーする。2階層目がある場合は参照渡しになる。
Object.assign
やスプレッド構文でできる。 -
ディープコピー
オブジェクトの値そのものをコピーする。ディープコピーする方法によっては問題があるので注意。
オブジェクトのプロパティに関数がなければstructuredClone()
、関数なども含めたディープコピーをしたいのであればlodash
のcloneDeep
を使うのが良さそう。
参考
Author And Source
この問題について([JavaScript]シャローコピーとディープコピーを理解する), 我々は、より多くの情報をここで見つけました https://zenn.dev/takuya_naganuma/articles/8c4a7e68cd58e6著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Collection and Share based on the CC protocol