kintone JavaScriptカスタマイズにおけるレコードオブジェクトの取り扱い（Shallowコピー・Deepコピー）

下記記事でイミュータブルにオブジェクトを扱うことについて触れていますが、その前提としてオブジェクトは参照型のデータ構造であるなどの説明が省かれているので今回その補足です。
※kintone JavaScriptカスタマイズを初学者に教えるにあたって理解してもらいたいポイントなので参照先として記載します。

https://qiita.com/_muraham/items/5f6528ae5258721483de

## 1. JavaScriptにおけるレコードの表現

kintoneのカスタマイズやプラグイン開発では、レコードをJavaScriptのオブジェクトとして扱います。

```javascript
const record = {
  名前: {
    value: "山田太郎"
  },
  年齢: {
    value: 30
  },
  部署: {
    value: "営業部"
  }
};
```

## 2. オブジェクトのコピーにおける注意点

JavaScriptのオブジェクトをコピーする際には、重要な注意点があります。

単純な代入では、同じオブジェクトへの参照が作成されるだけです。これを「浅いコピー」(Shallow copy)と呼びます。

例:

```javascript
const original = { name: "太郎" };
const copy = original;

copy.name = "花子";
console.log(original.name); // "花子" と表示されます
```

`original`を直接変更していないにもかかわらず、値が変わっています。これが浅いコピーの特性です。

## 3. kintoneカスタマイズにおける留意事項

kintoneのカスタマイズにおいても、この特性に注意する必要があります。

例:

```javascript
const updateRecord = (rec) => {
  rec.部署.value = "総務部";
};

updateRecord(record);
console.log(record.部署.value); // "総務部" と表示されます
```

`updateRecord`関数内で`rec`を変更すると、元の`record`オブジェクトも変更されます。

### 3.1 ユーザー定義関数での副作用

自身で定義した関数においても、Shallow copyによる副作用が発生する可能性があります。以下に例を示します：

```javascript
const formatName = (record) => {
  // 意図せずに元のレコードを変更してしまう
  record.名前.value = record.名前.value.toUpperCase();
  return `${record.名前.value} さん`;
};

const myRecord = {
  名前: {
    value: "山田太郎"
  }
};

console.log(formatName(myRecord)); // "山田太郎 さん" と表示
console.log(myRecord.名前.value);  // "山田太郎" ではなく "山田太郎" と表示されてしまう
```

この例では、`formatName`関数内で`record`オブジェクトを直接変更しているため、元の`myRecord`オブジェクトも変更されてしまいます。これは意図しない副作用を引き起こす可能性があります。

## 4. 深いコピーの実装方法

オブジェクトを完全に独立してコピーしたい場合は、「深いコピー」(Deep copy)を使用します。

最新のJavaScript環境では、`structuredClone()`関数が利用可能です：

```javascript
const originalRecord = {
  名前: {
    value: "山田太郎"
  },
  年齢: {
    value: 30
  }
};

const deepCopy = structuredClone(originalRecord);

deepCopy.名前.value = "鈴木花子";
console.log(originalRecord.名前.value); // "山田太郎"
console.log(deepCopy.名前.value); // "鈴木花子"
```

この方法により、元のオブジェクトを保持したまま、コピーしたオブジェクトのみを変更することが可能です。

## 5. kintoneカスタマイズにおける実践的な使用例

kintoneのカスタマイズでは、以下のように活用できます：

```javascript
kintone.events.on('app.record.create.submit', (event) => {
  const record = event.record;
  
  // レコードの直接変更（浅いコピー）
  record.部署.value = record.部署.value + "配属";
  
  return event;
});
```

元のレコードの状態を保持したい場合：

```javascript
kintone.events.on('app.record.create.submit', (event) => {
  const originalRecord = event.record;
  const copiedRecord = structuredClone(originalRecord);
  
  copiedRecord.部署.value = copiedRecord.部署.value + "配属";
  
  // copiedRecordを使用した処理
  
  event.record = copiedRecord;
  return event;
});
```

ユーザー定義関数での副作用を避ける例：

```javascript
const formatName = (record) => {
  // Deep copyを使用して副作用を防ぐ
  const copiedRecord = structuredClone(record);
  copiedRecord.名前.value = copiedRecord.名前.value.toUpperCase();
  return `${copiedRecord.名前.value} さん`;
};

const myRecord = {
  名前: {
    value: "山田太郎"
  }
};

console.log(formatName(myRecord)); // "山田太郎 さん" と表示
console.log(myRecord.名前.value);  // "山田太郎" と表示（元のレコードは変更されていない）
```

## まとめ

- kintoneカスタマイズでは、レコードをJavaScriptのオブジェクトとして操作します。
- オブジェクトの単純な代入は、同一オブジェクトへの参照を生成します（浅いコピー）。
- 浅いコピーは、ユーザー定義関数を含む様々な場面で予期せぬ副作用を引き起こす可能性があります。
- オブジェクトを完全に独立してコピーするには、深いコピーを使用します。
- 深いコピーの実装には`structuredClone()`関数が有効です。
- カスタマイズでは、状況に応じて浅いコピーと深いコピーを適切に使い分けることが重要です。

これらの概念を理解し、適切に応用することで、オブジェクトが参照型であるゆえのバグを防ぐことができます。