「グルーピングは lodash、ディープコピーは structuredClone がなくて自作、末尾要素は arr[arr.length - 1]」——そんな書き方、2026年はもう卒業できます。
ここ数年でJavaScriptには標準機能だけで完結する強力なAPIが続々と追加され、その大半がすでに全モダンブラウザとNode.jsで動くBaseline入りしました。
この記事では、実務で「知っていると一段速くなる」モダンJS機能を12個、すべてコピペで動くコード付きで紹介します。ライブラリを1つ減らせるものばかりです。
1. Object.groupBy() —— 配列のグルーピングが1行
「配列をカテゴリごとに分けたい」ときの定番。これまで reduce の呪文か lodash の groupBy に頼っていた処理が、標準APIで1行に。
const items = [
{ name: "りんご", type: "果物" },
{ name: "にんじん", type: "野菜" },
{ name: "みかん", type: "果物" },
];
const grouped = Object.groupBy(items, (item) => item.type);
// {
// 果物: [{ name: "りんご", ... }, { name: "みかん", ... }],
// 野菜: [{ name: "にんじん", ... }],
// }
// キーにオブジェクトを使いたいなら Map.groupBy()
const byLen = Map.groupBy(["a", "bb", "cc"], (s) => s.length);
// Map(2) { 1 => ["a"], 2 => ["bb", "cc"] }戻り値のオブジェクトは null プロトタイプなので、キーが toString などと衝突しない安全設計。Baseline(2024年3月〜)で、Node.js は 21 以降で使えます。
2. toSorted / toReversed / with —— 元配列を壊さない
sort() や reverse() は元の配列を破壊するのが罠でした。React の state などで「気づかず元データを書き換えてバグる」あるあるです。ES2023 で追加されたコピー版を使えば安心。
const nums = [3, 1, 2];
const sorted = nums.toSorted((a, b) => a - b); // [1, 2, 3]
const reversed = nums.toReversed(); // [2, 1, 3]
const replaced = nums.with(0, 99); // [99, 1, 2]
console.log(nums); // [3, 1, 2] ← 元配列はそのまま!
// state を安全に更新(React などで有効)
setTodos((prev) => prev.with(index, { ...prev[index], done: true }));toSpliced() もあり、これで「非破壊の splice」が書けます。全モダンブラウザ・Node 20+ で Baseline。
3. findLast / findLastIndex —— 末尾から探す
「条件に合う最後の要素」を取りたいとき、これまでは reverse() してから find()——でも reverse は破壊的。専用メソッドなら一発です。
const logs = [
{ level: "info", msg: "start" },
{ level: "error", msg: "boom" },
{ level: "info", msg: "retry" },
{ level: "error", msg: "fatal" },
];
const lastError = logs.findLast((l) => l.level === "error");
// { level: "error", msg: "fatal" }
const lastErrorIdx = logs.findLastIndex((l) => l.level === "error"); // 34. at(-1) —— 末尾アクセスがスマートに
arr[arr.length - 1] の定型句とはお別れ。負のインデックスで末尾から数えられます。文字列にも使えます。
const arr = [10, 20, 30];
arr.at(-1); // 30
arr.at(-2); // 20
"hello".at(-1); // "o"5. Set の集合演算 —— union / intersection / difference
「2つのリストの共通要素」「片方だけにある要素」を、for ループなしで。数学の集合演算がそのままメソッドに。
const a = new Set([1, 2, 3, 4]);
const b = new Set([3, 4, 5]);
a.union(b); // Set {1, 2, 3, 4, 5} 和集合
a.intersection(b); // Set {3, 4} 積集合(共通)
a.difference(b); // Set {1, 2} 差集合(aだけ)
a.symmetricDifference(b); // Set {1, 2, 5} 対称差
a.isSubsetOf(b); // false a は b の部分集合か?
a.isDisjointFrom(b); // false 交わりが無いか?引数は Set 以外の任意のイテラブルでも動きます(内部で Set として扱われます)。Baseline(2024年6月〜)。
正確なメソッド名は union / intersection / difference / symmetricDifference / isSubsetOf / isSupersetOf / isDisjointFrom の7つです。
6. Iterator Helpers —— 巨大データを遅延処理
配列メソッドの map/filter は都度、中間配列を作るのが弱点。イテレータヘルパーは遅延評価(必要な分だけ計算)なので、巨大データや無限シーケンスでもメモリを食いません。
// 100万件でも、必要な3件しか処理されない
const result = Array(1_000_000)
.keys() // イテレータを取得
.filter((n) => n % 2 === 0)
.map((n) => n * 10)
.take(3) // ここで打ち切り
.toArray(); // [0, 20, 40]
// 無限ジェネレータも安全に扱える
function* naturals() {
let i = 1;
while (true) yield i++;
}
const first5 = naturals().take(5).toArray(); // [1, 2, 3, 4, 5]map / filter / take / drop / flatMap / reduce / toArray / forEach / some / every / find が使えます。Baseline(2025年3月〜)、Node.js 22+。
7. Promise.withResolvers() —— Deferred が標準API化
「Promise の resolve/reject を外側から呼びたい」という Deferred パターン。これまでは new Promise の中で変数に代入する定型句が必要でしたが、標準APIになりました。
// Before: おなじみの定型句
// let resolve, reject;
// const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
// After:
const { promise, resolve, reject } = Promise.withResolvers();
button.addEventListener("click", () => resolve("clicked!"), { once: true });
const value = await promise; // クリックされるまで待つイベントやストリームを「1回きりの Promise」に橋渡しするときに便利。Baseline(2024年3月〜)、Node.js 22+。
8. Array.fromAsync() —— 非同期イテラブルを配列化
Array.from() の非同期版。for await...of で回していた非同期イテラブルを、そのまま配列にまとめられます。
async function* fetchPages() {
for (let page = 1; page <= 3; page++) {
const res = await fetch(`/api/items?page=${page}`);
yield await res.json();
}
}
// 全ページを1つの配列に(await を1回書くだけ)
const pages = await Array.fromAsync(fetchPages());
// Promise の配列にも使える(順番に await される)
const data = await Array.fromAsync([fetch("/a"), fetch("/b")]);Promise.all と違い逐次的に解決される点に注意(同時実行したいなら Promise.all)。Chrome/Firefox/Safari の現行版・Node.js 22+ で利用可能。
9. structuredClone() —— ライブラリなしのディープコピー
JSON.parse(JSON.stringify(obj)) の限界(Date が文字列化、Map/Set が壊れる、循環参照で例外)を克服する標準API。ネストしたオブジェクトを完全に複製します。
const original = {
when: new Date(),
tags: new Set(["a", "b"]),
meta: new Map([["k", "v"]]),
nested: { deep: [1, 2, 3] },
};
const copy = structuredClone(original);
copy.nested.deep.push(4);
original.nested.deep; // [1, 2, 3] ← 影響なし
copy.when instanceof Date; // true(Date もちゃんと維持)循環参照もOK。ただし関数・DOMノード・プロトタイプはコピーできない(例外になる)点だけ注意。ブラウザ・Node.js 17+ で Baseline。
10. Intl.Segmenter —— 絵文字を正しく数える
"👨👩👧".length は 8、スプレッド [..."👨👩👧"].length でも複数に割れてしまう——絵文字や結合文字を「見た目1文字」として数えるのは意外と難問です。Intl.Segmenter が解決します。
const seg = new Intl.Segmenter("ja", { granularity: "grapheme" });
const count = (str) => [...seg.segment(str)].length;
count("👨👩👧"); // 1(家族の絵文字も1文字)
count("café"); // 4
"👨👩👧".length; // 8 ← 素朴に数えると壊れる
// 単語単位の分割にも使える
const words = new Intl.Segmenter("ja", { granularity: "word" });
[...words.segment("今日は良い天気")]
.filter((s) => s.isWordLike)
.map((s) => s.segment); // ["今日", "は", "良い", "天気"]文字数制限のバリデーションや、日本語のワードカウントに効きます。全モダンブラウザ・Node.js で Baseline。
11. Promise.try() —— 同期例外も Promise に乗せる
「同期で例外を投げるかもしれない関数」を安全に呼びたいとき。Promise.resolve().then(fn) だと1ティック遅れますが、Promise.try は同期・非同期どちらの関数でも例外を必ず Promise の reject に変換してくれます。
function parse(input) {
if (!input) throw new Error("入力が空です"); // 同期例外
return JSON.parse(input);
}
// 同期エラーも非同期エラーも、全部 catch で受けられる
Promise.try(parse, userInput)
.then((data) => render(data))
.catch((err) => showError(err.message));
// async 関数と同期関数が混在する処理を統一的に書ける
await Promise.try(() => maybeAsyncTask());ES2025 の新機能。Chrome 128+ / Firefox 132+ / Safari 18.2+ / Node.js 22+ で利用可能です。
12. 論理代入演算子 ??= / ||= / &&=
地味ですが毎日使える3つ。「値が無いときだけ代入」を最短で書けます。
// null / undefined のときだけ代入
options.timeout ??= 3000;
// falsy(0や""含む)のときだけ代入
name ||= "ゲスト";
// truthy のときだけ代入
user.token &&= refresh(user.token);
// キャッシュ初期化の定番
const cache = {};
function get(key) {
return (cache[key] ??= expensiveCompute(key));
}??= は「0 や空文字は有効な値として残したい」ケースで ||= より安全。使い分けが肝心です。
落とし穴:JavaScriptに @supports は無い
CSS と違い、JS機能には @supports のような宣言的なフォールバックがありません。古い環境も相手にするなら、機能検出で分岐するか、ビルドツール(Babel / SWC)+ core-js のポリフィルでトランスパイルするのが基本です。
// 実行時の機能検出
const groupBy =
typeof Object.groupBy === "function"
? Object.groupBy
: (arr, fn) => arr.reduce((acc, x) => {
(acc[fn(x)] ??= []).push(x);
return acc;
}, {});とはいえ本記事の大半はすでに Baseline(=主要3ブラウザで安定利用可)。ターゲットが「最新2年のブラウザ+Node 22 LTS」なら、ほぼそのまま書いて問題ありません。導入前に一度 caniuse や MDN で対象ブラウザを確認しておくと安心です。
まとめ:ライブラリの前に、まず標準を疑おう
この10年で JavaScript の標準機能は劇的に強くなりました。グルーピング・集合演算・遅延イテレータ・ディープコピー・文字境界——かつて lodash やユーティリティ関数で埋めていた穴は、今や言語自身が埋めています。
- 配列操作は「非破壊版(toSorted / with)」を第一候補に
- グルーピングは Object.groupBy、集合演算は Set のメソッド
- 巨大データや無限列は Iterator Helpers で遅延処理
- ディープコピーは structuredClone、文字数カウントは Intl.Segmenter
新しいコードを書く前に「これ、標準でできないか?」と一度立ち止まる。それだけで依存パッケージが減り、バンドルは軽く、コードは読みやすくなります。まずは今日、1つ試してみてください。
型と組み合わせてさらに堅牢にしたい人は、脱・型初心者|TypeScript実践テク10選【2026】 もあわせてどうぞ。





