【JavaScript】使える正規表現テクニック20選

javascript JavaScript

あなたも、複雑な文字列操作に頭を抱えたことはありませんか?「もっと効率的に処理できないだろうか」と悩んだ経験はないでしょうか。多くのWebエンジニアが直面するこの問題に、正規表現は強力な解決策を提供します。しかし、その難解な構文に躊躇している方も少なくないはずです。本記事では、JavaScriptにおける正規表現の基礎から応用まで、実践的な20の例を通じて徹底的に解説します。これらのテクニックを習得すれば、あなたのコーディングスキルは劇的に向上するでしょう。さあ、正規表現の魔法を使いこなし、効率的で柔軟な文字列処理の世界へ飛び込みましょう!

正規表現の基礎

正規表現(Regular Expression)は、文字列のパターンを記述するための強力なツールです。JavaScriptでは、RegExpオブジェクトまたはリテラル表記(/.../)を使用して正規表現を作成できます。

正規表現の作成方法

// リテラル表記
const regexLiteral = /pattern/;

// RegExpオブジェクト
const regexObject = new RegExp('pattern');

どちらの方法も同じ結果を得られますが、リテラル表記の方がより簡潔で読みやすいため、多くの場合で推奨されます。

基本的なメタ文字

正規表現では、特別な意味を持つメタ文字を使用してパターンを記述します。以下に主要なメタ文字を紹介します:

  • .: 任意の1文字にマッチ
  • ^: 行の先頭にマッチ
  • $: 行の末尾にマッチ
  • *: 直前の文字の0回以上の繰り返しにマッチ
  • +: 直前の文字の1回以上の繰り返しにマッチ
  • ?: 直前の文字の0回または1回の出現にマッチ
  • \: エスケープ文字(特殊文字を通常の文字として扱う)

文字クラス

文字クラスを使用すると、特定の文字セットにマッチさせることができます:

  • [abc]: a, b, cのいずれかにマッチ
  • [^abc]: a, b, c以外の任意の文字にマッチ
  • [a-z]: aからzまでの小文字にマッチ
  • [A-Z]: AからZまでの大文字にマッチ
  • [0-9]: 0から9までの数字にマッチ

量指定子

量指定子を使用すると、パターンの繰り返し回数を指定できます:

  • {n}: 直前の文字がちょうどn回繰り返す
  • {n,}: 直前の文字がn回以上繰り返す
  • {n,m}: 直前の文字がn回以上m回以下繰り返す

実践的な正規表現テクニック20選

それでは、実際のコーディングシーンで役立つ20の正規表現テクニックを見ていきましょう。

メールアドレスの検証

メールアドレスの基本的な形式を検証する正規表現:

const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
console.log(emailRegex.test('user@example.com')); // true
console.log(emailRegex.test('invalid-email')); // false

この正規表現は、一般的なメールアドレスの形式をカバーしていますが、完全に厳密ではありません。実際のアプリケーションでは、より複雑な検証や、メールアドレスの実在性の確認が必要になる場合があります。

パスワード強度の検証

最低8文字、大文字小文字、数字、特殊文字を含むパスワードを検証する正規表現:

const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
console.log(passwordRegex.test('Str0ngP@ssword')); // true
console.log(passwordRegex.test('weakpass')); // false

この正規表現は、先読み肯定表現((?=...))を使用して、パスワードが特定の条件を満たしているかをチェックしています。

URLの検証

基本的なURLの形式を検証する正規表現:

const urlRegex = /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/;
console.log(urlRegex.test('https://www.example.com')); // true
console.log(urlRegex.test('invalid-url')); // false

この正規表現は、httpやhttpsで始まるURLや、www.から始まるドメイン名、様々なトップレベルドメインに対応しています。

日付形式の検証(YYYY-MM-DD)

YYYY-MM-DD形式の日付を検証する正規表現:

const dateRegex = /^\d{4}-([0]\d|1[0-2])-([0-2]\d|3[01])$/;
console.log(dateRegex.test('2023-05-15')); // true
console.log(dateRegex.test('2023-13-32')); // false

この正規表現は、年(4桁)、月(01-12)、日(01-31)の基本的な範囲をチェックしています。ただし、各月の正確な日数(例:2月は28または29日まで)までは検証していないので、より厳密な検証が必要な場合は追加のロジックが必要です。

電話番号の検証(日本の場合)

日本の電話番号形式を検証する正規表現:

const phoneRegex = /^(0\d{1,4}-?\d{1,4}-?\d{4})$/;
console.log(phoneRegex.test('03-1234-5678')); // true
console.log(phoneRegex.test('090-1234-5678')); // true
console.log(phoneRegex.test('1234-5678')); // false

この正規表現は、固定電話と携帯電話の一般的な形式に対応しています。ハイフンの有無も許容しています。

郵便番号の検証(日本の場合)

日本の郵便番号形式を検証する正規表現:

const postalCodeRegex = /^\d{3}-?\d{4}$/;
console.log(postalCodeRegex.test('123-4567')); // true
console.log(postalCodeRegex.test('1234567')); // true
console.log(postalCodeRegex.test('123-456')); // false

この正規表現は、3桁-4桁の形式と、ハイフンなしの7桁の形式両方に対応しています。

IPアドレスの検証

IPv4アドレスを検証する正規表現:

const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
console.log(ipv4Regex.test('192.168.0.1')); // true
console.log(ipv4Regex.test('256.1.2.3')); // false

この正規表現は、各オクテットが0から255の範囲内にあることを確認します。

HTMLタグの抽出

HTMLタグを抽出する正規表現:

const htmlString = '<p>This is a <strong>bold</strong> text.</p>';
const tagRegex = /<[^>]+>/g;
console.log(htmlString.match(tagRegex)); // ['<p>', '<strong>', '</strong>', '</p>']

この正規表現は、開始タグと終了タグの両方を抽出します。ただし、HTMLの解析には専用のパーサーを使用することが推奨されます。

文字列内の数値の抽出

文字列から数値を抽出する正規表現:

const text = 'The price is $19.99 and the quantity is 5';
const numberRegex = /-?\d+(\.\d+)?/g;
console.log(text.match(numberRegex)); // ['19.99', '5']

この正規表現は、整数と小数の両方を抽出します。マイナス記号も考慮されています。

キャメルケースからスネークケースへの変換

キャメルケースの文字列をスネークケースに変換する正規表現:

const camelToSnake = (str) => str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
console.log(camelToSnake('camelCaseString')); // 'camel_case_string'

この正規表現は大文字を検出し、アンダースコアと小文字に置き換えています。

文字列内の重複単語の検出

文字列内の連続する重複単語を検出する正規表現:

const text = 'The the quick brown fox jumps over the lazy dog dog';
const duplicateRegex = /\b(\w+)\s+\1\b/g;
console.log(text.match(duplicateRegex)); // ['the the', 'dog dog']

この正規表現は、キャプチャグループと後方参照を使用して、連続する同じ単語を検出します。

文字列の先頭と末尾の空白を削除

文字列の先頭と末尾の空白を削除する正規表現:

const trim = (str) => str.replace(/^\s+|\s+$/g, '');
console.log(trim('  Hello, World!  ')); // 'Hello, World!'

この正規表現は、文字列の先頭(^)または末尾($)にある1つ以上の空白文字(\s+)を削除します。

URLからクエリパラメータの抽出

URLからクエリパラメータを抽出する正規表現:

const url = 'https://example.com/page?name=John&age=30';
const paramRegex = /[?&]([^=]+)=([^&]*)/g;
const params = {};
url.replace(paramRegex, (_, key, value) => {
    params[key] = decodeURIComponent(value);
});
console.log(params); // { name: 'John', age: '30' }

この正規表現は、URLのクエリ文字列から各パラメータのキーと値を抽出します。

文字列内のHTMLエンティティのエスケープ

特殊文字をHTMLエンティティにエスケープする正規表現:

const escapeHTML = (str) => str.replace(/[&<>"']/g, (m) => ({
    '&': '&',
    '<': '<',
    '>': '>',
    '"': '"',
    "'": '''
})[m]);

console.log(escapeHTML('<p>"Hello & World!"</p>')); // '<p>"Hello & World!"</p>'

この正規表現は、HTML内で特別な意味を持つ文字をエスケープします。

文字列内の特定の単語の置換

文字列内の特定の単語を別の単語に置換する正規表現:

const text = 'The quick brown fox jumps over the lazy dog';
const replaceWord = (str, oldWord, newWord) => str.replace(new RegExp(`\\b${oldWord}\\b`, 'gi'), newWord);
console.log(replaceWord(text, 'fox', 'cat')); // 'The quick brown cat jumps over the lazy dog'

この正規表現は、単語の境界(\b)を使用して、完全な単語のみを置換します。giフラグにより、大文字小文字を区別せずにグローバルに置換を行います。

カスタム日付フォーマットの解析

カスタムな日付フォーマットを解析する正規表現:

const parseDate = (dateString, format) => {
    const parts = {};
    const regex = format.replace(/[YMD]/g, (match) => {
        const regexPart = {
            'Y': '(\\d{4})',
            'M': '(\\d{1,2})',
            'D': '(\\d{1,2})'
        }[match];
        return `(?<${match}>${regexPart})`;
    });
    const match = dateString.match(new RegExp(regex));
    if (match) {
        return new Date(match.groups.Y, parseInt(match.groups.M) - 1, match.groups.D);
    }
    return null;
};

console.log(parseDate('2023/05/15', 'Y/M/D')); // Mon May 15 2023 ...
console.log(parseDate('15-05-2023', 'D-M-Y')); // Mon May 15 2023 ...

この例では、動的に正規表現を構築し、名前付きキャプチャグループを使用して日付の各部分を抽出しています。

文字列内の数式の計算

文字列内の簡単な数式を計算する正規表現:

const calculateExpression = (expr) => {
    const regex = /(-?\d+(?:\.\d+)?)\s*([\+\-\*\/])\s*(-?\d+(?:\.\d+)?)/;
    while (regex.test(expr)) {
        expr = expr.replace(regex, (_, a, op, b) => {
            const result = {
                '+': (x, y) => x + y,
                '-': (x, y) => x - y,
                '*': (x, y) => x * y,
                '/': (x, y) => x / y
            }[op](parseFloat(a), parseFloat(b));
            return result.toString();
        });
    }
    return expr;
};

console.log(calculateExpression('5 + 3 * 2')); // '11'
console.log(calculateExpression('10 / 2 - 3')); // '2'

この例では、正規表現を使用して数式の各部分(数値と演算子)を抽出し、順番に計算しています。ただし、この方法は演算子の優先順位を考慮していないため、複雑な数式の場合は適切ではありません。

文字列内のカラーコードの抽出

文字列内のHEXカラーコードを抽出する正規表現:

const extractColorCodes = (text) => {
    const colorRegex = /#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})\b/g;
    return text.match(colorRegex) || [];
};

const text = 'The color #FF5733 is vibrant, while #000 is black.';
console.log(extractColorCodes(text)); // ['#FF5733', '#000']

この正規表現は、6桁または3桁のHEXカラーコードを抽出します。

文字列内のMarkdownリンクの解析

Markdownリンクを解析する正規表現:

const parseMarkdownLinks = (markdown) => {
    const linkRegex = /\[([^\]]+)\]\(([^\)]+)\)/g;
    const links = [];
    let match;
    while ((match = linkRegex.exec(markdown)) !== null) {
        links.push({ text: match[1], url: match[2] });
    }
    return links;
};

const markdown = 'Check out [Google](https://www.google.com) and [GitHub](https://github.com).';
console.log(parseMarkdownLinks(markdown));
// [
//   { text: 'Google', url: 'https://www.google.com' },
//   { text: 'GitHub', url: 'https://github.com' }
// ]

この正規表現は、Markdownリンクの形式([テキスト](URL))を解析し、リンクテキストとURLを抽出します。

文字列内の時間表現の標準化

さまざまな時間表現を24時間形式に標準化する正規表現:

const standardizeTime = (timeString) => {
    const timeRegex = /(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/i;
    const match = timeString.match(timeRegex);
    if (match) {
        let [_, hours, minutes = '00', ampm] = match;
        hours = parseInt(hours);
        if (ampm) {
            if (ampm.toLowerCase() === 'pm' && hours < 12) hours += 12;
            if (ampm.toLowerCase() === 'am' && hours === 12) hours = 0;
        }
        return `${hours.toString().padStart(2, '0')}:${minutes}`;
    }
    return null;
};

console.log(standardizeTime('3:30pm')); // '15:30'
console.log(standardizeTime('11 am')); // '11:00'
console.log(standardizeTime('14:45')); // '14:45'

この正規表現は、12時間形式と24時間形式の両方を解析し、24時間形式に標準化します。

正規表現のパフォーマンスと最適化

正規表現は強力なツールですが、不適切な使用はパフォーマンスの低下を招く可能性があります。以下に、正規表現のパフォーマンスを向上させるためのいくつかのヒントを紹介します。

過度な後方参照を避ける

後方参照(\1, \2など)の過度な使用は、正規表現エンジンに大きな負荷をかける可能性があります。可能な限り、名前付きキャプチャグループ((?<name>...))を使用することを検討してください。

貪欲な量指定子の使用を最小限に

*+などの貪欲な量指定子の使用は、バックトラッキングを引き起こし、パフォーマンスを低下させる可能性があります。可能な場合は、非貪欲な量指定子(*?+?)を使用するか、より具体的なパターンを記述してください。

適切なアンカーの使用

^(行頭)や$(行末)などのアンカーを適切に使用することで、不要なマッチングを避け、パフォーマンスを向上させることができます。

過度に複雑な正規表現を避ける

1つの複雑な正規表現よりも、複数の単純な正規表現を組み合わせて使用する方が、多くの場合パフォーマンスが向上します。

正規表現オブジェクトの再利用

頻繁に使用する正規表現は、オブジェクトとして一度だけ作成し、再利用することをお勧めします。

const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
// この正規表現オブジェクトを繰り返し使用する

正規表現のテストとデバッグ

正規表現のテストとデバッグは、複雑なパターンを扱う際に非常に重要です。以下に、役立つツールとテクニックを紹介します。

オンラインツールの活用

  • Regex101:リアルタイムで正規表現をテストし、詳細な説明を提供します。
  • RegExr:正規表現の学習、作成、テストのための優れたツールです。

JavaScriptコンソールの使用

ブラウザの開発者ツールやNode.jsのREPLを使用して、正規表現をインタラクティブにテストできます。

> /hello/.test('hello world')
true
> 'hello world'.match(/\w+/g)
['hello', 'world']

ユニットテストの作成

正規表現を使用するコードには、必ずユニットテストを書くことをお勧めします。Jestなどのテストフレームワークを使用して、さまざまな入力に対する正規表現の動作を確認できます。

test('email validation', () => {
  const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
  expect(emailRegex.test('user@example.com')).toBe(true);
  expect(emailRegex.test('invalid-email')).toBe(false);
});

まとめ

JavaScriptにおける正規表現は、文字列操作と検証のための強力なツールです。本記事で紹介した20のテクニックは、日々のコーディングタスクを効率化し、よりクリーンで堅牢なコードを書く助けとなるでしょう。正規表現の学習曲線は急かもしれませんが、その威力を理解し、適切に使用できるようになれば、プログラミングの可能性が大きく広がります。

ただし、正規表現は万能ではありません。複雑すぎる正規表現は、コードの可読性を損ない、メンテナンスを困難にする可能性があります。また、HTMLやXMLの解析など、特定のタスクには専用のパーサーを使用することが推奨されます。

正規表現の学習は継続的なプロセスです。新しいパターンや技術を学び、実践し、そして他の開発者と知識を共有することで、あなたのスキルは着実に向上していくでしょう。

最後に、正規表現を使用する際は、常にセキュリティを意識することが重要です。ユーザー入力を直接正規表現に使用する場合は、適切なサニタイズを行い、潜在的な脆弱性を防ぐようにしてください。

Happy coding!

参考リンク

Regular-Expressions.info

MDN Web Docs: Regular Expressions

JavaScript.info: Regular expressions

コメント

タイトルとURLをコピーしました