©️

Typescript シャローコピー・ディープコピー

2024/12/09に公開

TypescriptのObject.isでシャローコピーとディープコピーについて理解したので学習したことをまとめます。

Object.is(arg1, arg2)

引数で受け取った2つのオブジェクトを比較し、一致するかを判定します。
この比較は参照レベルで行われます。

type OriginObj = {
  name: string;
  age: number;
  contact: {
    email: string;
    phone?: string;
  };
};

const originObj: OriginObj = {
  name: "田中太郎",
  age: 30,
  contact: {
    email: "tanaka@example.com",
    phone: "090-1234-5678",
  },
};

const ref = originObj;
Object.is(originObj, ref) // true

シャローコピー

浅いコピーともいわれます。const shallow = {...obj}のように、スプレッド構文でコピーするとシャローコピーになります。
このコピーは、オブジェクトの第一階層まではコピーされますが、第二階層(オブジェクトの中にさらにオブジェクトがある場合)は参照が渡されます。
つまり、第二階層以降はコピーではなく、元と同じもの(参照レベルで)。

type OriginObj = {
  name: string;
  age: number;
  contact: {
    email: string;
    phone?: string;
  };
};

const originObj: OriginObj = {
  name: "田中太郎",
  age: 30,
  contact: {
    email: "tanaka@example.com",
    phone: "090-1234-5678",
  },
};
const shallowObj = { ...originObj };

console.log(shallowObj);
// 出力
// 見かけ上は同じ
// {
//   name: "田中太郎",
//   age: 30,
//   contact: {
//     email: "tanaka@example.com",
//     phone: "090-1234-5678",
//   }
// }

// この比較はfalse
Object.is(originObj, shallowObj);

// この比較はtrue
Object.is(originObj.contact, shallowObj.contact);

ディープコピー

シャローコピーとは異なり、すべての階層のコピーを取る。
JSON.parce + JSON.stringifyで実現できるが、これだと型がanyになってしまいます。

type OriginObj = {
  name: string;
  age: number;
  contact: {
    email: string;
    phone?: string;
  };
};

const originObj: OriginObj = {
  name: "田中太郎",
  age: 30,
  contact: {
    email: "tanaka@example.com",
    phone: "090-1234-5678",
  },
};

const deepObj = JSON.parse(JSON.stringify(originObj));

console.log(deepObj);
// 出力
// 見かけ上は当然同じ
// {
//   name: "田中太郎",
//   age: 30,
//   contact: {
//     email: "tanaka@example.com",
//     phone: "090-1234-5678",
//   }
// }

// この比較はfalse
Object.is(originObj, deepObj);

// この比較もfalse
Object.is(originObj.contact, deepObj.contact);

// anyなので存在しないプロパティにアクセスしてもエラーにならない!
deepObj.hontohanai;

structuredClone

anyを回避する方法として、structuredCloneがあります。

type OriginObj = {
  name: string;
  age: number;
  contact: {
    email: string;
    phone?: string;
  };
};

const originObj: OriginObj = {
  name: "田中太郎",
  age: 30,
  contact: {
    email: "tanaka@example.com",
    phone: "090-1234-5678",
  },
};

const deepObj = structuredClone(originObj);

// この比較はfalse
Object.is(originObj, deepObj);

// この比較もfalse
Object.is(originObj.contact, deepObj.contact);

// エラーになる!
deepObj.hontohanai;

ただしこれも万能というわけではなく、関数や独自クラスのインスタンス、Symbolはコピーできません。lodashというライブラリを使うことでも解決できるらしいですが、今回は触れません。

Discussion