概要
JavaScriptにおけるデータ型は、その値が変更可能(ミュータブル)か、あるいは変更不可能(イミュータブル)かという性質によって2つに大別されます。この性質を正しく理解していないと、意図しない場所でデータが書き換わるなどの副作用に悩まされることになります。本記事では、プリミティブ型とオブジェクト型の挙動の違いを整理し、安全なデータ管理の方法を解説します。
仕様(入出力)
| カテゴリ | 特性 | 対象となる型 | 意味 |
| プリミティブ型 | イミュータブル(不変) | Number, String, Boolean, Null, undefined, Symbol | 一度生成された値そのものを変更することはできません。変数の値を書き換える際は、新しい値で「再代入」されます。 |
| オブジェクト型 | ミュータブル(可変) | Object, Array, Function | オブジェクトの構造を維持したまま、内部のプロパティや要素を直接書き換えることができます。 |
基本の使い方
プリミティブ型である数値や文字列を操作する場合、それは常に「新しい値の生成」となります。一方で、配列やオブジェクトなどのオブジェクト型は、同じメモリ領域を指したまま内部の状態を書き換えることが可能です。このため、複数の変数で一つのオブジェクトを共有している場合、一方の操作が他方に影響を与える「副作用」に注意する必要があります。
コード全文(HTML / JAVASCRIPT)
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Environmental Monitoring System</title>
</head>
<body>
<div id="monitor-app">
<h2>環境監視データ管理</h2>
<div id="log-display">
<p>現在の閾値: <span id="threshold-val">--</span></p>
<p>最新ログ: <span id="latest-log">--</span></p>
</div>
<hr>
<button id="btn-update-primitive">閾値を更新(再代入)</button>
<button id="btn-mutate-object">ログ配列を操作(破壊的更新)</button>
</div>
<script type="module" src="main.js"></script>
</body>
</html>
JavaScript
/**
* 監視システムの管理スクリプト
* 担当者: mori
*/
// 1. プリミティブ型(イミュータブル)の例
// 数値そのものは変更できないため、変数を let で宣言して「再代入」を行う
let alertThreshold = 25;
// 2. オブジェクト型(ミュータブル)の例
// 配列の中身を直接書き換えるため、変数自体が指す対象は変わらない(constで定義可能)
const temperatureLogs = [22, 24, 23];
const thresholdElement = document.getElementById('threshold-val');
const logElement = document.getElementById('latest-log');
/** UI表示を更新する関数 */
const updateUI = () => {
thresholdElement.textContent = `${alertThreshold}℃`;
logElement.textContent = temperatureLogs.join(', ');
};
// プリミティブ型の操作:新しい値を生成して代入
document.getElementById('btn-update-primitive').addEventListener('click', () => {
// 25 という値が 30 に変わるのではなく、新しい 30 という値が変数に格納される
alertThreshold = 30;
console.log('閾値を再代入しました:', alertThreshold);
updateUI();
});
// オブジェクト型の操作:既存のメモリ領域にあるデータを書き換え(ミュータブル)
document.getElementById('btn-mutate-object').addEventListener('click', () => {
// 配列の先頭要素を直接書き換え(破壊的な操作)
temperatureLogs[0] = 100;
// 新しい要素を追加
temperatureLogs.push(26);
console.log('配列内部を直接書き換えました:', temperatureLogs);
updateUI();
});
// 初期表示
updateUI();
カスタムポイント
letとconstを適切に使い分け、再代入が必要なプリミティブ型にはletを、構造を維持するオブジェクト型にはconstを使用して意図を明確にしてください。- 配列操作において、元のデータを保護したい場合は
pushではなくスプレッド構文[...logs, newValue]を用いた非破壊的な生成への変更を検討してください。
注意点
- プリミティブ型を「変更」しようとする操作(例:文字列の特定の文字だけを書き換える)は失敗し、エラーにならない場合でも元の値が維持されます。
- オブジェクト型のミュータブルな性質により、関数に配列を渡して内部を書き換えると、呼び出し元のデータも変化します。これは共有渡しと呼ばれる挙動であり、大規模開発では予期せぬバグの温床となります。
constは「再代入」を禁止するものであり、オブジェクトの「内部変更」を禁止するものではないという点に留意してください。
応用
文字列(イミュータブル)を操作しようとした際の挙動と、正しい更新方法の例です。
/**
* ユーザー名の整形処理
* 担当者: mori
*/
let userName = "mori";
// 文字列はイミュータブルなので、インデックス指定での書き換えはできない
userName[0] = "M";
console.log(userName); // "mori" のまま(サイレントに失敗する)
// 正しくは、新しい文字列を生成して再代入する
userName = "M" + userName.slice(1);
console.log(userName); // "Mori"
まとめ
JavaScriptにおけるデータの扱いは、その型が持つ不変性(イミュータブル)と可変性(ミュータブル)という性質に支配されています。数値や文字列などのプリミティブ型は、値そのものが書き換わることはなく常に新しい状態への置き換えが発生します。一方、配列やオブジェクトはメモリ上の実体を共有したまま内部の状態を更新できるため、柔軟な操作が可能である反面、意図しないデータの不整合を招くリスクを孕んでいます。開発者はこれらの特性を意識し、状況に応じて再代入による更新と内部プロパティの操作を適切に選択する必要があります。
