概要
JavaScriptの Set は、重複しない値のコレクションを表すオブジェクトである。
ES2015 (ES6) で導入され、同じ値を複数回追加しても1つしか保持されない。
値の比較にはSameValueZeroアルゴリズムが使用される。
このアルゴリズムでは、NaN 同士を同一視し、+0 と -0 を同一視する。
Set は挿入順を保持する。
反復処理を行うと、要素は挿入された順序で列挙される。
has() メソッドによる検索はO(1)で実行される。
配列の includes() メソッドがO(N)であるのに対し、大規模なデータに対して高速な検索が可能である。
ES2025では集合演算メソッドが7種追加され、和集合・積集合・差集合・対称差集合の演算と、部分集合・上位集合・素集合の判定がネイティブに行えるようになった。
これらのメソッドはChrome 122以降、Firefox 127以降、Safari 17以降、Edge 122以降でサポートされている。(Baseline 2024)
Setの作成
Set を作成するには new Set() コンストラクタを使用する。
コンストラクタ
コンストラクタの構文を以下に示す。
new Set()- 空の
Setを作成する。
- 空の
new Set(iterable)- イテラブルオブジェクト (配列、文字列等) から
Setを初期化する。 - 重複する値は自動的に除去される。
- イテラブルオブジェクト (配列、文字列等) から
// 空のSetを作成
const emptySet = new Set();
console.log(emptySet.size); // 0
// 配列からSetを作成 (重複は自動除去)
const numSet = new Set([1, 2, 3, 2, 1]);
console.log(numSet); // Set(3) { 1, 2, 3 }
console.log(numSet.size); // 3
// 文字列もイテラブルとして展開される
const charSet = new Set("hello");
console.log(charSet); // Set(4) { 'h', 'e', 'l', 'o' }
// 様々な型の値を格納できる
const mixedSet = new Set([1, "hello", true, null, undefined]);
console.log(mixedSet.size); // 5
// SameValueZero の例: NaN 同士は同一視される
const nanSet = new Set([NaN, NaN, NaN]);
console.log(nanSet.size); // 1 (NaN は1つとして扱われる)
// SameValueZero の例: +0 と -0 は同一視される
const zeroSet = new Set([0, -0, +0]);
console.log(zeroSet.size); // 1
要素の操作
Set には要素を追加・確認・削除するためのメソッドが用意されている。
add
add(value) は、Set に値を追加するメソッドである。
戻り値は Set インスタンス自身であるため、メソッドチェーンで連続して呼び出すことができる。
既に存在する値を追加しようとした場合は、Set は変更されず、そのまま返される。
const set = new Set();
// 基本的な追加
set.add(1);
set.add(2);
set.add(3);
console.log(set); // Set(3) { 1, 2, 3 }
// メソッドチェーン (add はインスタンス自身を返すため可能)
const chainedSet = new Set()
.add("apple")
.add("banana")
.add("orange");
console.log(chainedSet); // Set(3) { 'apple', 'banana', 'orange' }
// 重複値は無視される
set.add(2);
set.add(2);
console.log(set.size); // 3 (変化なし)
has
has(value) は指定した値が Set に含まれているかどうかを真偽値で返すメソッドである。
SameValueZeroアルゴリズムで比較するため、NaN も正しく検索できる。
検索の時間計算量は であり、要素数によらず一定時間で結果が返る。
const set = new Set([1, 2, 3, NaN]);
console.log(set.has(1)); // true
console.log(set.has(4)); // false
console.log(set.has(NaN)); // true (SameValueZeroによりNaNを正しく検索できる)
// 型が異なれば false
console.log(set.has("1")); // false (数値 1 と文字列 "1" は別物)
delete と clear
delete(value) は指定した値を Set から削除するメソッドである。
削除に成功した場合は true、指定した値が存在しなかった場合は false を返す。
clear() は Set の全要素を削除するメソッドである。
戻り値は undefined である。
const set = new Set([1, 2, 3, 4, 5]);
// delete : 指定した値を削除
console.log(set.delete(3)); // true (削除成功)
console.log(set.delete(9)); // false (存在しないため失敗)
console.log(set); // Set(4) { 1, 2, 4, 5 }
// clear : 全要素を削除
set.clear();
console.log(set.size); // 0
console.log(set); // Set(0) {}
sizeプロパティ
size は、Set に含まれる要素数を返す読み取り専用プロパティである。
配列の length プロパティに相当するが、書き込みはできない。
const set = new Set([10, 20, 30]);
console.log(set.size); // 3
set.add(40);
console.log(set.size); // 4
set.delete(10);
console.log(set.size); // 3
重複排除への活用
Set の重複排除の特性を活用することにより、配列の重複を簡潔に除去できる。
配列の重複排除
プリミティブ値 (数値、文字列、真偽値等) の配列に対しては、Set を使った重複排除が効果的である。
スプレッド構文または Array.from() を使用して、重複排除後の配列を得ることができる。
const numbers = [1, 2, 3, 2, 1, 4, 3, 5];
// スプレッド構文を使用した重複排除
const unique1 = [...new Set(numbers)];
console.log(unique1); // [1, 2, 3, 4, 5]
// Array.from()を使用した重複排除
const unique2 = Array.from(new Set(numbers));
console.log(unique2); // [1, 2, 3, 4, 5]
// 文字列の配列
const words = ["apple", "banana", "apple", "orange", "banana"];
const uniqueWords = [...new Set(words)];
console.log(uniqueWords); // ["apple", "banana", "orange"]
// 挿入順が保持される
const letters = ["c", "a", "b", "a", "c"];
console.log([...new Set(letters)]); // ["c", "a", "b"]
オブジェクト配列の重複排除
オブジェクトは参照で比較されるため、Set による単純な重複排除はできない。
Map をキー指定の重複排除に使用するパターンが有効である。
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 1, name: "Alice (duplicate)" },
{ id: 3, name: "Charlie" },
{ id: 2, name: "Bob (duplicate)" }
];
// Mapを使用して、idをキーに重複排除
// 同じidが複数ある場合、後から登場したものが優先される
const uniqueUsers = [...new Map(users.map((u) => [u.id, u])).values()];
console.log(uniqueUsers);
// [
// { id: 1, name: "Alice (duplicate)" },
// { id: 2, name: "Bob (duplicate)" },
// { id: 3, name: "Charlie" }
// ]
// 最初に登場したものを優先したい場合は逆順にしてから変換する
const uniqueUsersFirst = [
...new Map([...users].reverse().map((u) => [u.id, u])).values()
].reverse();
console.log(uniqueUsersFirst);
// [
// { id: 1, name: "Alice" },
// { id: 2, name: "Bob" },
// { id: 3, name: "Charlie" }
// ]
反復処理
Set はイテラブルであり、複数の方法で反復処理できる。
forEach
forEach(callback) は Set の各要素に対してコールバック関数を実行するメソッドである。
コールバック関数の引数は (value, value, set) の形式である。
Set にはキーの概念がないため、第1引数と第2引数の両方に値が渡される。
これは、Map の forEach との API 互換性を保つための仕様である。
const set = new Set(["apple", "banana", "orange"]);
// forEachのコールバック引数は (value, value, set)
set.forEach((value, valueAgain, setRef) => {
console.log(value, valueAgain); // 第1引数と第2引数は同じ値
// "apple" "apple"
// "banana" "banana"
// "orange" "orange"
});
// 実用的な使用例
const total = new Set([10, 20, 30]);
let sum = 0;
total.forEach((value) => {
sum += value;
});
console.log(sum); // 60
for...of
for...of ループを使用して、Set の要素を反復処理することができる。
挿入順に要素が列挙される。
const set = new Set([1, 2, 3, 4, 5]);
for (const value of set) {
console.log(value);
// 1, 2, 3, 4, 5 (挿入順)
}
// 分割代入との組み合わせ
const coordSet = new Set([[0, 0], [1, 2], [3, 4]]);
for (const coord of coordSet) {
const [x, y] = coord;
console.log(`x=${x}, y=${y}`);
}
keys / values / entries
Set は、keys()、values()、entries() の3つのイテレータメソッドを持つ。
各メソッドの動作を以下に示す。
| メソッド | 動作 |
|---|---|
values() |
Set の値を挿入順で列挙するイテレータを返す。
|
keys() |
values() と同一のイテレータを返す。Map との API 互換性のために存在する。
|
entries() |
[value, value] 形式の配列を挿入順で列挙するイテレータを返す。Map との API 互換性のために、キーと値の両方に同じ値が入る。
|
const set = new Set(["a", "b", "c"]);
// values(), keys()は、同じ結果を返す
console.log([...set.values()]); // ["a", "b", "c"]
console.log([...set.keys()]); // ["a", "b", "c"] (values と同一)
// entries()は、[value, value]形式
for (const entry of set.entries()) {
console.log(entry);
// ["a", "a"]
// ["b", "b"]
// ["c", "c"]
}
配列への変換
Set を配列に変換するには、スプレッド構文または Array.from() を使用する。
const set = new Set([10, 20, 30]);
// スプレッド構文
const arr1 = [...set];
console.log(arr1); // [10, 20, 30]
// Array.from()
const arr2 = Array.from(set);
console.log(arr2); // [10, 20, 30]
// Array.from()は、マッピング関数も指定できる
const doubled = Array.from(set, (value) => value * 2);
console.log(doubled); // [20, 40, 60]
集合演算 (ES2025)
ES2025で導入された集合演算メソッドにより、2つの Set に対する集合演算をネイティブに実行できる。
全メソッドは元の Set を変更せず、新しい Set を返す。
パラメータには Set だけでなく、Set のようなオブジェクト (hasメソッド / keysメソッド / sizeプロパティを持つオブジェクト) も指定できる。
union
union(other) は和集合 を返すメソッドである。
2つの Set のいずれか一方または両方に含まれる全ての要素を持つ新しい Set を返す。
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// 和集合 : A ∪ B
const unionSet = setA.union(setB);
console.log(unionSet); // Set(6) { 1, 2, 3, 4, 5, 6 }
// 元のSetは変更されない
console.log(setA); // Set(4) { 1, 2, 3, 4 }
console.log(setB); // Set(4) { 3, 4, 5, 6 }
intersection
intersection(other) は積集合 を返すメソッドである。
2つの Set の両方に含まれる要素だけを持つ新しい Set を返す。
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// 積集合 : A ∩ B
const intersectionSet = setA.intersection(setB);
console.log(intersectionSet); // Set(2) { 3, 4 }
difference
difference(other) は差集合 を返すメソッドである。
呼び出し元の Set には含まれるが、引数の Set には含まれない要素を持つ新しい Set を返す。
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// 差集合 : A - B (A には含まれるが B には含まれない)
const diffAB = setA.difference(setB);
console.log(diffAB); // Set(2) { 1, 2 }
// 差集合 : B - A (B には含まれるが A には含まれない)
const diffBA = setB.difference(setA);
console.log(diffBA); // Set(2) { 5, 6 }
symmetricDifference
symmetricDifference(other) は対称差集合 を返すメソッドである。
一方の Set にのみ含まれる要素 (両方に含まれる要素を除外したもの) を持つ新しい Set を返す。
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// 対称差集合 : A △ B (どちらか一方にのみ含まれる)
const symDiff = setA.symmetricDifference(setB);
console.log(symDiff); // Set(4) { 1, 2, 5, 6 }
isSubsetOf / isSupersetOf
isSubsetOf(other) は呼び出し元の Set が引数の Set の部分集合かどうかを判定するメソッドである。
呼び出し元の全要素が引数の Set に含まれている場合に true を返す。
isSupersetOf(other) は呼び出し元の Set が引数の Set の上位集合かどうかを判定するメソッドである。
引数の全要素が呼び出し元の Set に含まれている場合に true を返す。
const setA = new Set([1, 2, 3]);
const setB = new Set([1, 2, 3, 4, 5]);
// isSubsetOf : A が B の部分集合か
console.log(setA.isSubsetOf(setB)); // true (Aの全要素がBに含まれる)
console.log(setB.isSubsetOf(setA)); // false (Bの全要素がAに含まれない)
// isSupersetOf : A が B の上位集合か
console.log(setB.isSupersetOf(setA)); // true (BはAの全要素を含む)
console.log(setA.isSupersetOf(setB)); // false (AはBの全要素を含まない)
// 自分自身は部分集合でもあり上位集合でもある
console.log(setA.isSubsetOf(setA)); // true
console.log(setA.isSupersetOf(setA)); // true
isDisjointFrom
isDisjointFrom(other) は2つの Set が素集合 (共通要素を持たない) かどうかを判定するメソッドである。
共通する要素が1つもない場合に true を返す。
const setA = new Set([1, 2, 3]);
const setB = new Set([4, 5, 6]);
const setC = new Set([3, 4, 5]);
// isDisjointFrom: 共通要素がなければ true
console.log(setA.isDisjointFrom(setB)); // true (共通要素なし)
console.log(setA.isDisjointFrom(setC)); // false (3 が共通)
console.log(setB.isDisjointFrom(setC)); // false (4, 5 が共通)
下表に、ES2025で追加されたSetメソッドの一覧を示す。
| メソッド名 | 戻り値の型 | 説明 |
|---|---|---|
union(other) |
Set |
和集合 いずれか一方または両方に含まれる要素 |
intersection(other) |
Set |
積集合 両方に含まれる要素のみ |
difference(other) |
Set |
差集合 Aにのみ含まれる要素 |
symmetricDifference(other) |
Set |
対称差集合 どちらか一方にのみ含まれる要素 |
isSubsetOf(other) |
boolean |
AがBの部分集合かどうかを判定 |
isSupersetOf(other) |
boolean |
AがBの上位集合かどうかを判定 |
isDisjointFrom(other) |
boolean |
AとBに共通要素がないかどうかを判定 |
WeakSet
WeakSetとは
WeakSet は Set に似たコレクションであるが、格納できる値はオブジェクト (または未登録の Symbol) のみに限られる。
WeakSet の主な特徴を以下に示す。
- 弱参照
- 格納されたオブジェクトへの参照は弱参照である。他に参照がなくなった場合、ガベージコレクション (GC) によって自動的に回収される。
- 反復処理不可
forEach、for...of、keys()、values()等の反復処理メソッドは存在しない。
sizeプロパティなし- GC のタイミングにより要素数が変動するため、
sizeプロパティは存在しない。
- GC のタイミングにより要素数が変動するため、
clear()メソッドなし- 全要素の削除機能はない。
基本操作
WeakSet には add、has、delete の3つのメソッドが用意されている。
const weakSet = new WeakSet();
const obj1 = { name: "Alice" };
const obj2 = { name: "Bob" };
// add : オブジェクトを追加
weakSet.add(obj1);
weakSet.add(obj2);
// has : 含まれているか確認
console.log(weakSet.has(obj1)); // true
console.log(weakSet.has(obj2)); // true
// delete : 削除
weakSet.delete(obj1);
console.log(weakSet.has(obj1)); // false
// プリミティブ値は追加できない (TypeErrorが発生する)
// weakSet.add(1); // TypeError
// weakSet.add("str"); // TypeError
使用例
WeakSet の代表的な使用パターンを示す。
循環参照検出パターンでは、オブジェクトグラフの走査中に既に訪れたオブジェクトを追跡するために WeakSet を使用する。
// 循環参照を検出しながらオブジェクトを走査する例
function deepStringify(obj, visited = new WeakSet()) {
if (typeof obj !== "object" || obj === null) {
return JSON.stringify(obj);
}
if (visited.has(obj)) {
return '"[Circular]"'; // 循環参照を検出
}
visited.add(obj);
const entries = Object.entries(obj).map(([key, value]) => {
return `"${key}": ${deepStringify(value, visited)}`;
});
return `{ ${entries.join(", ")} }`;
}
const a = { name: "Alice" };
const b = { name: "Bob", partner: a };
a.partner = b; // 循環参照を作成
console.log(deepStringify(a));
// { "name": "Alice", "partner": { "name": "Bob", "partner": "[Circular]" } }
オブジェクト追跡パターンでは、オブジェクトの状態を追跡するために WeakSet を使用する。
// 処理済みオブジェクトを追跡する例
const processedItems = new WeakSet();
function processItem(item) {
if (processedItems.has(item)) {
console.log("既に処理済みです");
return;
}
// 処理を実行
console.log(`Processing: ${item.name}`);
processedItems.add(item);
}
const item1 = { name: "Task A" };
processItem(item1); // Processing: Task A
processItem(item1); // 既に処理済みです
// item1への参照が全てなくなると、WeakSetからもGCで回収される
下表に、Set と WeakSet の比較を示す。
| 項目 | Set | WeakSet |
|---|---|---|
| 格納できる値 | 任意の値 | オブジェクトのみ (または未登録Symbol) |
| 参照の種類 | 強参照 | 弱参照 |
| ガベージコレクション | GCの対象外 (参照を保持) | GCの対象 (他に参照がなければ回収) |
size プロパティ |
あり | なし |
clear() メソッド |
あり | なし |
| 反復処理 | 可能 (forEach, for...of 等) |
不可能 |
| 主な用途 | 汎用的なコレクション、重複排除、集合演算 | 循環参照検出、オブジェクト追跡 |
Set と Array の比較
Set と Array はどちらも値のコレクションを扱うが、それぞれ異なる特性を持つ。
| 項目 | Set | Array |
|---|---|---|
| 重複 | 不可 (自動除去) | 可能 |
| 順序 | 挿入順を保持 | インデックス順を保持 |
| 要素へのアクセス | has() による存在確認のみ |
インデックスによる直接アクセス可能 |
| 検索速度 | : 要素数に関わらず一定 | O(N) : 要素数に比例
|
| 大規模データでの検索 | 非常に高速 | 要素数が増えると低速 |
| メモリ使用量 | Arrayより多い傾向 | 少ない傾向 |
| 反復処理 | forEach, for...of |
forEach, for...of, map, filter 等
|
| 集合演算 | ネイティブサポート (ES2025) | 手動実装が必要 |
| 用途 | 重複排除、集合演算、高速な存在確認 | 順序が重要なデータ、インデックスアクセス |
使い分けの指針
Set と Array の使い分けの指針を以下に示す。
| 型 | 場面 | 理由 |
|---|---|---|
Set |
重複を含まないことを保証したい場合 | 一意な値のコレクションを管理する場合に適している。 |
| 大規模データに対して頻繁に存在確認を行う場合 | has() メソッドが で動作するため、100件を超えるデータに対する存在確認では Set が有利である。
| |
| 集合演算が必要な場合 (ES2025以降) | 和集合、積集合、差集合等の演算を行う場合は、Set のネイティブメソッドを使用する。
| |
Array |
インデックスによる要素アクセスが必要な場合 | arr[0] のようなインデックスアクセスは、Array のみに可能である。
|
| 重複を許可する必要がある場合 | 同じ値を複数回格納する必要がある場合は、Array を使用する。
| |
map、filter、reduce 等の変換・集約処理が必要な場合 |
Set にはこれらのメソッドがないため、変換処理が多い場合は、 Array の方が適している。
| |
| 小規模データ (100件未満程度) の存在確認 | この規模では、Array の includes() と Set の has() の速度差は無視できる。
|
関連情報
- JavaScriptの基礎 - Map
- Map、WeakMap、Map.groupBy
- JavaScriptの基礎 - イテレータとジェネレータ
- イテレータプロトコル、ジェネレータ、ES2025 Iterator Helpers
- JavaScriptの基礎 - 配列の操作
- 配列の作成、要素の追加・削除、検索
- JavaScriptの基礎 - 配列の反復メソッド
- forEach, map, filter, reduce等の反復処理メソッド