JavaScriptのコードを読んでいると、頻繁に登場するドット3つ「…」という記法。
これはモダンJavaScript(ES6以降)において、コードを簡潔かつ読みやすくするために欠かせない非常に強力な機能です。
この記法には、大きく分けて「スプレッド構文」と「残余引数」という2つの役割があります。
一見すると同じように見えますが、使われるコンテキスト(文脈)によってその働きは真逆になります。
この記事では、2026年現在の開発現場でも必須知識となっている「…」の基本から応用、そして注意点までを詳しく解説します。
ドット3つ「…」の正体:スプレッド構文と残余引数
JavaScriptにおけるドット3つは、その使い道によって呼び名が変わります。
一つは、配列やオブジェクトの中身を「展開」するスプレッド構文(Spread Syntax)。
もう一つは、複数の要素を一つの配列として「集約」する残余引数(Rest Parameters)です。
これらを利用することで、従来のJavaScriptで必要だった冗長なループ処理や複雑な配列操作を、驚くほどスマートに記述できるようになりました。
まずはそれぞれの基本的な挙動から整理していきましょう。
スプレッド構文(Spread Syntax)の活用術
スプレッド構文は、反復可能なオブジェクト(配列や文字列など)を、個々の要素にバラバラに展開する機能です。
バラバラにする、つまり「封筒から中身を取り出す」ようなイメージを持つと理解しやすいでしょう。
配列での活用:コピーと結合
配列の操作において、スプレッド構文は最も頻繁に利用されます。
従来のconcat()メソッドなどを使用するよりも、直感的で宣言的な記述が可能です。
配列のコピー
配列を別の変数にコピーしたい場合、単に代入(const arr2 = arr1)すると参照渡しになってしまいますが、スプレッド構文を使えば新しい配列としてコピーできます。
// 配列のコピー
const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log("Original:", original);
console.log("Copy:", copy);
Original: [1, 2, 3]
Copy: [1, 2, 3, 4]
配列の結合と要素の追加
複数の配列を結合したり、特定の場所に新しい要素を差し込んだりするのも簡単です。
const fruits = ["apple", "banana"];
const vegetables = ["carrot", "potato"];
// 2つの配列を結合し、新しい要素も追加
const food = ["meat", ...fruits, "fish", ...vegetables];
console.log(food);
["meat", "apple", "banana", "fish", "carrot", "potato"]
オブジェクトでの活用:プロパティの展開と上書き
オブジェクトに対してもスプレッド構文は利用可能です(ES2018で導入)。
これは現代のフロントエンド開発、特にReactなどのライブラリにおける状態管理(State Management)で極めて重要な役割を果たします。
オブジェクトのコピーと更新
既存のオブジェクトの一部だけを変更した新しいオブジェクトを作成する場合に便利です。
const user = {
id: 1,
name: "田中",
role: "admin"
};
// nameだけを書き換えた新しいオブジェクトを作成
const updatedUser = {
...user,
name: "佐藤",
age: 25 // 新しいプロパティの追加
};
console.log(updatedUser);
{ id: 1, name: "佐藤", role: "admin", age: 25 }
ここで重要なのは、記述する順番です。
スプレッド構文で展開した後に同じプロパティ名を記述すると、後の値が優先(上書き)されます。
逆に、スプレッド構文を後に書くと、元の値で上書きされてしまうため注意が必要です。
関数呼び出しでの活用
配列の各要素を関数の引数として渡したい場合、従来はapply()メソッドを使用していました。
スプレッド構文を使えば、これを非常に簡潔に記述できます。
const numbers = [10, 50, 30, 90, 20];
// Math.maxに配列をそのまま渡すとNaNになるが、展開すればOK
const maxVal = Math.max(...numbers);
console.log(maxVal);
90
残余引数(Rest Parameters)で柔軟な関数を作る
スプレッド構文が「展開」だったのに対し、残余引数(Rest Parameters)は「集約」を行います。
関数の引数定義でドット3つを使うことで、不特定の数の引数を一つの配列として受け取ることができます。
可変長引数の処理
引数の数が決まっていない関数を定義する際、以前はargumentsという特殊なオブジェクトが使われていましたが、これは配列ではないため操作が不便でした。
残余引数を使えば、最初から配列として扱えます。
// 全ての引数を合計する関数
function sumAll(...numbers) {
// numbersは配列なので、reduceメソッドなどがそのまま使える
return numbers.reduce((acc, curr) => acc + curr, 0);
}
console.log(sumAll(1, 2, 3));
console.log(sumAll(10, 20, 30, 40, 50));
6
150
分割代入との組み合わせ(Rest Property)
残余引数は、分割代入(Destructuring assignment)と組み合わせて、「残りの要素」を抽出する際にも非常に便利です。
配列の分割代入
const colors = ["red", "green", "blue", "yellow", "pink"];
// 最初の2つを取り出し、残りを一箇所にまとめる
const [first, second, ...restColors] = colors;
console.log(first); // red
console.log(second); // green
console.log(restColors); // ["blue", "yellow", "pink"]
オブジェクトの分割代入
特定のプロパティを除外した残りのデータセットを作成したい場合に重宝します。
const product = {
id: "P100",
name: "高性能PC",
price: 150000,
stock: 15
};
// idとstockを除いた「残りの情報」をまとめる
const { id, stock, ...displayInfo } = product;
console.log(displayInfo);
{ name: "高性能PC", price: 150000 }
スプレッド構文と残余引数の決定的な違い
一見同じ「…」ですが、その役割を見極めるポイントは「イコールのどちら側にあるか」や「括弧内の位置」です。
| 特徴 | スプレッド構文(Spread) | 残余引数(Rest) |
|---|---|---|
| 主な役割 | 配列やオブジェクトを展開する | 複数の値を一つに集約する |
| 使用場所 | 配列リテラル、オブジェクトリテラル、関数呼び出しの引数 | 関数の引数定義、分割代入の受け取り側 |
| イメージ | 封筒から中身を出す | 散らばった物を袋に詰める |
この違いを意識するだけで、コードを読む際の理解スピードが格段に上がります。
実践的な応用パターンとベストプラクティス
2026年のJavaScript開発において、「…」を単に使うだけでなく、パフォーマンスや副作用を考慮した使い方が求められます。
イミュータブル(不変)な状態更新
ReactやVue.jsなどのモダンフレームワークでは、元のデータを直接書き換えずに新しいデータを作成する「イミュータビリティ」の概念が重要です。
スプレッド構文は、これを実現するための標準的な手法です。
const [items, setItems] = useState([{ id: 1, text: "Learn JS" }]);
const addItem = (newText) => {
// 元のitemsを破壊せず、新しい配列を生成してセットする
setItems([...items, { id: Date.now(), text: newText }]);
};
このように記述することで、データの変更検知を正確に行い、予期せぬバグを防ぐことができます。
浅いコピー(Shallow Copy)の罠と対策
初心者が最も陥りやすい罠が、「スプレッド構文は浅いコピー(Shallow Copy)しか行わない」という点です。
オブジェクトが階層構造(ネスト)になっている場合、第1階層は新しいオブジェクトになりますが、第2階層以降は参照が維持されたままになります。
const deepObject = {
a: 1,
b: { c: 2 }
};
const copy = { ...deepObject };
// ネストされた階層を変更すると...
copy.b.c = 99;
// コピー元まで書き換わってしまう!
console.log(deepObject.b.c);
99
これを防ぐには、階層ごとにスプレッド構文を適用するか、structuredClone()関数を使用して深いコピー(Deep Copy)を行う必要があります。
// 対策1:階層ごとにスプレッド
const correctCopy = {
...deepObject,
b: { ...deepObject.b }
};
// 対策2:モダンなDeep Copy(推奨)
const safeCopy = structuredClone(deepObject);
パフォーマンスへの配慮
スプレッド構文は非常に便利ですが、巨大な配列(数万要素以上)に対して頻繁に実行すると、メモリ消費や処理速度に影響を与える場合があります。
特にループ内でのスプレッド構文による配列結合(arr = [...arr, newItem])は、計算量が $O(n^2)$ になりやすいため、大量のデータを扱う際はpush()などの破壊的メソッドを適切にカプセル化して使用する検討も必要です。
JavaScriptの未来とドット3つの進化
2026年現在、JavaScriptの仕様検討(ECMAScript提案)では、さらなる柔軟な展開構文の拡張も議論されています。
例えば、パイプライン演算子との組み合わせや、より高度なパターンマッチングにおいて、この「…」という記号はさらに重要な地位を占めるようになっています。
もはやドット3つを使わずにモダンなコードを書くことは不可能と言っても過言ではありません。
ライブラリのドキュメントを読み解く上でも、この構文の仕組みを深く理解しておくことは、シニアエンジニアへの第一歩となります。
まとめ
本記事では、JavaScriptのドット3つ「…」について、スプレッド構文と残余引数の両面から解説しました。
- スプレッド構文は、配列やオブジェクトをバラバラに展開し、コピーや結合、関数の引数渡しに利用します。
- 残余引数は、複数の値を一つの配列にまとめ、可変長引数の処理や分割代入での余り物の抽出に利用します。
- イミュータビリティを維持するために不可欠な道具ですが、浅いコピーの性質には注意が必要です。
これらの特性を正しく理解し、使い分けることで、あなたの書くJavaScriptコードはより簡潔で、メンテナンス性の高いものへと進化します。
日々のコーディングの中で「ここは展開(Spread)か、集約(Rest)か」を意識しながら、この便利な機能を使いこなしていきましょう。
