Tasuke Hubのロゴ

ITを中心に困っている人を助けるメディア

分かりやすく解決策を提供することで、あなたの困ったをサポート。 全ての人々がスムーズに生活できる世界を目指します。

JavaScript ES2024新機能完全ガイド!モダン開発で差をつける最新構文と実装例

記事のサムネイル

ES2024の概要と開発環境への影響

JavaScript ES2024(ECMAScript 2024)は、JavaScriptの最新仕様として多くの実用的な新機能を導入しています。従来のコードをより読みやすく、効率的に書けるようになります。

主要ブラウザでのサポート状況は以下の通りです:

// ブラウザサポート確認方法
if (Array.prototype.toSorted) {
  console.log('ES2024の toSorted() がサポートされています');
}

if (Object.groupBy) {
  console.log('ES2024の Object.groupBy() がサポートされています');
}

開発環境でES2024を使用するには、以下の設定が推奨されます:

// package.json
{
  "engines": {
    "node": ">=20.0.0"
  },
  "browserslist": [
    "last 2 Chrome versions",
    "last 2 Firefox versions",
    "last 2 Safari versions"
  ]
}
// .babelrc.js (必要に応じてトランスパイル)
module.exports = {
  presets: [
    ['@babel/preset-env', {
      targets: { browsers: ['> 1%', 'last 2 versions'] }
    }]
  ]
};

Array.prototype.toSorted()で配列操作を効率化

従来のsort()メソッドは元の配列を変更してしまいますが、ES2024のtoSorted()は新しい配列を返すため、イミュータブルな操作が可能になります。

// 従来のsort()(元の配列を変更)
const numbers = [3, 1, 4, 1, 5];
const sorted = numbers.sort(); // numbersも変更される
console.log(numbers); // [1, 1, 3, 4, 5]
console.log(sorted);  // [1, 1, 3, 4, 5]

// ES2024のtoSorted()(元の配列は変更されない)
const originalNumbers = [3, 1, 4, 1, 5];
const newSorted = originalNumbers.toSorted();
console.log(originalNumbers); // [3, 1, 4, 1, 5] (変更されない)
console.log(newSorted);       // [1, 1, 3, 4, 5]

実際の開発でよく使われるパターンの例:

// ユーザーデータのソート例
const users = [
  { name: '田中', score: 85 },
  { name: '佐藤', score: 92 },
  { name: '山田', score: 78 }
];

// スコア順にソート(元データは保持)
const sortedByScore = users.toSorted((a, b) => b.score - a.score);

// 複数の条件でソート
const products = [
  { name: 'ノートPC', price: 80000, category: 'PC' },
  { name: 'マウス', price: 3000, category: 'PC' },
  { name: 'キーボード', price: 5000, category: 'PC' }
];

const sortedProducts = products.toSorted((a, b) => {
  // カテゴリー順、その後価格順
  if (a.category !== b.category) {
    return a.category.localeCompare(b.category);
  }
  return a.price - b.price;
});

toSorted()のメリット:

  • 元の配列を変更しない(イミュータブル)
  • Reactの状態管理で安全に使用可能
  • 関数型プログラミングのパラダイムに適合### Object.groupBy()によるデータグルーピングの新手法

ES2024で追加されたObject.groupBy()は、配列の要素を特定の条件でグループ化する機能です。従来はlodashのような外部ライブラリが必要でしたが、ネイティブで実現できるようになりました。

// 基本的な使用方法
const fruits = [
  { name: 'りんご', color: '赤' },
  { name: 'バナナ', color: '黄' },
  { name: 'いちご', color: '赤' },
  { name: 'レモン', color: '黄' },
  { name: 'ぶどう', color: '紫' }
];

// 色でグループ化
const groupedByColor = Object.groupBy(fruits, fruit => fruit.color);
console.log(groupedByColor);
// {
//   '赤': [
//     { name: 'りんご', color: '赤' },
//     { name: 'いちご', color: '赤' }
//   ],
//   '黄': [
//     { name: 'バナナ', color: '黄' },
//     { name: 'レモン', color: '黄' }
//   ],
//   '紫': [
//     { name: 'ぶどう', color: '紫' }
//   ]
// }

実際のWebアプリケーションでの活用例:

// eコマースサイトでの商品データ処理
const products = [
  { id: 1, name: 'iPhone 15', category: 'スマートフォン', price: 120000 },
  { id: 2, name: 'MacBook Pro', category: 'ノートPC', price: 200000 },
  { id: 3, name: 'iPad Air', category: 'タブレット', price: 80000 },
  { id: 4, name: 'Galaxy S24', category: 'スマートフォン', price: 110000 },
  { id: 5, name: 'Surface Pro', category: 'タブレット', price: 150000 }
];

// カテゴリー別にグループ化
const productsByCategory = Object.groupBy(products, p => p.category);

// 価格帯別にグループ化
const productsByPriceRange = Object.groupBy(products, product => {
  if (product.price < 100000) return '10万円未満';
  if (product.price < 150000) return '10-15万円';
  return '15万円以上';
});

// 複合条件でのグループ化例
const salesData = [
  { month: '2024-01', product: 'A', sales: 100 },
  { month: '2024-01', product: 'B', sales: 150 },
  { month: '2024-02', product: 'A', sales: 120 },
  { month: '2024-02', product: 'B', sales: 130 }
];

const groupedSales = Object.groupBy(salesData, item => `${item.month}-${item.product}`);

従来の方法との比較:

// 従来の方法(手動でreduce)
const traditionalGrouping = products.reduce((acc, product) => {
  const key = product.category;
  if (!acc[key]) {
    acc[key] = [];
  }
  acc[key].push(product);
  return acc;
}, {});

// ES2024の方法(シンプル)
const modernGrouping = Object.groupBy(products, p => p.category);
```### Promise.withResolvers()で非同期処理をシンプルに

ES2024の`Promise.withResolvers()`は、Promiseの作成パターンを簡素化します。従来のPromiseコンストラクタとは異なり、resolve/reject関数を別途取得できるため、より柔軟な非同期処理が可能です。

```javascript
// 従来のPromise作成方法
function createPromiseOld() {
  let resolve, reject;
  const promise = new Promise((res, rej) => {
    resolve = res;
    reject = rej;
  });
  return { promise, resolve, reject };
}

// ES2024のPromise.withResolvers()
function createPromiseNew() {
  return Promise.withResolvers();
}

const { promise, resolve, reject } = Promise.withResolvers();

実際の活用例:

// ファイルアップロード処理の例
class FileUploader {
  constructor() {
    this.uploadPromises = new Map();
  }

  async uploadFile(file) {
    const { promise, resolve, reject } = Promise.withResolvers();
    const uploadId = Date.now().toString();
    
    this.uploadPromises.set(uploadId, { resolve, reject });

    // FormDataを作成してアップロード
    const formData = new FormData();
    formData.append('file', file);

    try {
      const response = await fetch('/api/upload', {
        method: 'POST',
        body: formData
      });

      if (response.ok) {
        const result = await response.json();
        resolve(result);
      } else {
        reject(new Error('アップロードに失敗しました'));
      }
    } catch (error) {
      reject(error);
    } finally {
      this.uploadPromises.delete(uploadId);
    }

    return promise;
  }

  // アップロードをキャンセルする機能
  cancelUpload(uploadId) {
    const promiseHandlers = this.uploadPromises.get(uploadId);
    if (promiseHandlers) {
      promiseHandlers.reject(new Error('アップロードがキャンセルされました'));
      this.uploadPromises.delete(uploadId);
    }
  }
}

// 使用例
const uploader = new FileUploader();
const fileInput = document.querySelector('#file-input');

fileInput.addEventListener('change', async (event) => {
  const file = event.target.files[0];
  if (file) {
    try {
      const result = await uploader.uploadFile(file);
      console.log('アップロード成功:', result);
    } catch (error) {
      console.error('アップロードエラー:', error.message);
    }
  }
});

カスタムイベント待機の実装例:

// カスタムイベントを待機する関数
function waitForCustomEvent(eventName, timeout = 5000) {
  const { promise, resolve, reject } = Promise.withResolvers();
  
  const eventHandler = (event) => {
    cleanup();
    resolve(event.detail);
  };

  const timeoutId = setTimeout(() => {
    cleanup();
    reject(new Error(`${eventName}イベントがタイムアウトしました`));
  }, timeout);

  const cleanup = () => {
    document.removeEventListener(eventName, eventHandler);
    clearTimeout(timeoutId);
  };

  document.addEventListener(eventName, eventHandler, { once: true });
  
  return promise;
}

// 使用例
async function handleUserAction() {
  try {
    const result = await waitForCustomEvent('user-action-complete', 10000);
    console.log('ユーザーアクションが完了しました:', result);
  } catch (error) {
    console.error('ユーザーアクションが完了しませんでした:', error.message);
  }
}
```### Well-formed Unicode Stringsによる文字列処理の改善

ES2024では、Unicode文字列の処理が改善され、不正なUnicodeシーケンスの処理がより安全になりました。これにより、国際化アプリケーションでの文字列処理がより信頼性の高いものになります。

```javascript
// Well-formed Unicode Stringsの確認
function isWellFormed(str) {
  return str === str.toWellFormed();
}

// 文字列の修正
function fixUnicodeString(str) {
  return str.toWellFormed();
}

// 実際の使用例
const validString = "こんにちは世界";
const invalidString = "\uD800"; // 不正なサロゲートペア

console.log(isWellFormed(validString));  // true
console.log(isWellFormed(invalidString)); // false

console.log(fixUnicodeString(invalidString)); // "�" (置換文字に修正)

国際化対応アプリケーションでの活用:

// フォーム入力の文字列検証
class TextValidator {
  static validateAndClean(input) {
    if (typeof input !== 'string') {
      throw new Error('入力は文字列である必要があります');
    }

    // Unicode文字列を安全に処理
    const cleanInput = input.toWellFormed();
    
    return {
      original: input,
      cleaned: cleanInput,
      isValid: input === cleanInput,
      hasUnicodeIssues: input !== cleanInput
    };
  }

  static sanitizeUserInput(input) {
    const result = this.validateAndClean(input);
    
    if (result.hasUnicodeIssues) {
      console.warn('入力にUnicodeの問題が検出され、修正されました');
    }
    
    return result.cleaned;
  }
}

// 使用例
const userInput = "ユーザー入力テキスト\uD800不正文字";
const sanitized = TextValidator.sanitizeUserInput(userInput);
console.log('修正後:', sanitized);

// API通信でのデータ処理
async function sendUserData(userData) {
  // ユーザーデータの文字列フィールドを安全に処理
  const processedData = {};
  
  for (const [key, value] of Object.entries(userData)) {
    if (typeof value === 'string') {
      processedData[key] = value.toWellFormed();
    } else {
      processedData[key] = value;
    }
  }

  try {
    const response = await fetch('/api/user', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(processedData)
    });
    
    return response.json();
  } catch (error) {
    console.error('APIエラー:', error);
    throw error;
  }
}

正規表現との組み合わせ:

// 絵文字やUnicode文字を含むテキストの処理
function processTextWithEmojis(text) {
  // まず文字列を安全な形に修正
  const safeText = text.toWellFormed();
  
  // 絵文字の数をカウント
  const emojiRegex = /[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]/gu;
  const emojiCount = (safeText.match(emojiRegex) || []).length;
  
  // 文字数を正確にカウント(サロゲートペアを考慮)
  const characterCount = [...safeText].length;
  
  return {
    text: safeText,
    emojiCount,
    characterCount,
    byteLength: new TextEncoder().encode(safeText).length
  };
}

// 使用例
const textWithEmojis = "Hello 👋 World 🌍";
const result = processTextWithEmojis(textWithEmojis);
console.log(result);
// {
//   text: "Hello 👋 World 🌍",
//   emojiCount: 2,
//   characterCount: 13,
//   byteLength: 19
// }

実際のプロジェクトでの活用場面と注意点

ES2024の新機能を実際のプロジェクトで活用する際の具体的な場面と注意すべきポイントをまとめます。

React/Vue.jsプロジェクトでの活用例:

// Reactコンポーネントでの活用例
import React, { useState, useEffect } from 'react';

function ProductList({ products }) {
  const [sortedProducts, setSortedProducts] = useState([]);
  const [groupedProducts, setGroupedProducts] = useState({});

  useEffect(() => {
    // toSorted()でイミュータブルなソート
    const sorted = products.toSorted((a, b) => b.rating - a.rating);
    setSortedProducts(sorted);

    // Object.groupBy()でカテゴリー別グループ化
    const grouped = Object.groupBy(products, p => p.category);
    setGroupedProducts(grouped);
  }, [products]);

  return (
    <div>
      <h2>人気順商品</h2>
      {sortedProducts.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
      
      <h2>カテゴリー別商品</h2>
      {Object.entries(groupedProducts).map(([category, items]) => (
        <CategorySection key={category} category={category} products={items} />
      ))}
    </div>
  );
}

移行時の注意点とベストプラクティス:

  1. 段階的な導入: 新機能は段階的に導入し、重要な機能から始める
  2. テストの充実: 新機能を使用する部分は特に丁寧にテストを書く
  3. フォールバック戦略: 古いブラウザ対応が必要な場合はpolyfillまたは代替実装を用意
  4. パフォーマンス監視: 新機能導入後のパフォーマンス変化を監視
  5. チーム教育: チームメンバーに新機能の使い方と注意点を共有
// フィーチャー検出による安全な実装
function safeSortArray(array, compareFn) {
  if (Array.prototype.toSorted) {
    return array.toSorted(compareFn);
  }
  // フォールバック実装
  return [...array].sort(compareFn);
}

function safeGroupBy(array, keyFn) {
  if (Object.groupBy) {
    return Object.groupBy(array, keyFn);
  }
  // polyfillまたは手動実装
  return array.reduce((groups, item) => {
    const key = keyFn(item);
    if (!groups[key]) {
      groups[key] = [];
    }
    groups[key].push(item);
    return groups;
  }, {});
}

ES2024の新機能を活用することで、コードの可読性と保守性が大幅に向上します。ただし、プロダクション環境では適切なフォールバック戦略を用意し、段階的に導入することが重要です。

TH

Tasuke Hub管理人

東証プライム市場上場企業エンジニア

情報系修士卒業後、大手IT企業にてフルスタックエンジニアとして活躍。 Webアプリケーション開発からクラウドインフラ構築まで幅広い技術に精通し、 複数のプロジェクトでリードエンジニアを担当。 技術ブログやオープンソースへの貢献を通じて、日本のIT技術コミュニティに積極的に関わっている。

🎓情報系修士🏢東証プライム上場企業💻フルスタックエンジニア📝技術ブログ執筆者

このトピックはこちらの書籍で勉強するのがおすすめ!

この記事の内容をさらに深く理解したい方におすすめの一冊です。実践的な知識を身につけたい方は、ぜひチェックしてみてください!

おすすめ記事

おすすめコンテンツ