【JavaScript】大文字ごとにスペースで区切る方法|camelCase・PascalCase変換・正規表現replace対応

JavaScriptでアルファベットの大文字ごとにスペースで区切る処理は、camelCase や PascalCase の変数名を人間が読みやすい形に変換するときに頻出します。

この記事では、replace() と正規表現を使った基本テクニックから、連続大文字(HTMLParser → HTML Parser)の正しい処理、snake_case / kebab-case への相互変換、lodash との比較、実務での活用例まで網羅的に解説します。

この記事で学べること

  • replace() + 正規表現で大文字前にスペースを挿入する基本
  • camelCase・PascalCase をスペース区切りに変換する方法
  • 連続大文字(略語)を正しく処理するテクニック
  • snake_case / kebab-case への変換ユーティリティ
  • lodash(_.startCase)との比較
  • 実務での活用例(UI表示・ログ整形・テスト出力)
  • ブラウザ互換性とよくある落とし穴
スポンサーリンク

replace() + 正規表現で大文字前にスペースを挿入する基本

最もシンプルなアプローチは、String.prototype.replace() に正規表現 /([A-Z])/g を渡し、大文字の前にスペースを挿入する方法です。

JavaScript – 大文字前にスペースを挿入(基本)
const str = "HelloWorldInJavaScript";

// 大文字 [A-Z] の前にスペースを挿入
const result = str.replace(/([A-Z])/g, ' $1').trim();

console.log(result);
// "Hello World In Java Script"

実行結果

"Hello World In Java Script"

仕組みの解説

要素 説明
/([A-Z])/g 大文字 A〜Z にマッチ。() でキャプチャグループ化
' $1' スペース + キャプチャした大文字で置換
g フラグ 文字列内のすべての大文字に適用(グローバル)
.trim() 先頭にスペースが入る場合を除去

注意:先頭が大文字の PascalCase(例: "HelloWorld")の場合、先頭にもスペースが挿入されるため、.trim() が必須です。

camelCase をスペース区切りに変換する

camelCase(先頭が小文字で始まる)をスペース区切りに変換する場合、先頭の単語もそのまま保持されます。

JavaScript – camelCase → スペース区切り
function camelToSpaces(str) {
  return str.replace(/([A-Z])/g, ' $1').trim();
}

console.log(camelToSpaces("backgroundColor"));
console.log(camelToSpaces("fontSize"));
console.log(camelToSpaces("maxWidthValue"));
console.log(camelToSpaces("onClick"));

実行結果

"background Color"
"font Size"
"max Width Value"
"on Click"

camelCase の場合は先頭が小文字なので .trim() は不要ですが、PascalCase と統一的に扱うため付けておくと安全です。

PascalCase をスペース区切りに変換する

PascalCase(先頭も大文字)は React のコンポーネント名などで頻出します。先頭のスペースを除去するために .trim() を使うか、正規表現側で先頭文字を除外します。

JavaScript – PascalCase → スペース区切り(2パターン)
// パターン1: replace + trim(シンプル)
function pascalToSpaces(str) {
  return str.replace(/([A-Z])/g, ' $1').trim();
}

// パターン2: 先頭以外の大文字にのみマッチ(trim 不要)
function pascalToSpacesV2(str) {
  return str.replace(/(?<=[a-z])([A-Z])/g, ' $1');
}

console.log(pascalToSpaces("HelloWorld"));
console.log(pascalToSpacesV2("HelloWorld"));
console.log(pascalToSpaces("UserProfileSettings"));
console.log(pascalToSpacesV2("UserProfileSettings"));

実行結果

"Hello World"
"Hello World"
"User Profile Settings"
"User Profile Settings"

ポイント:パターン2の (?<=[a-z]) は「後読み(lookbehind)」で、小文字の後に続く大文字だけにマッチします。.trim() が不要になりコードが簡潔になります。

連続大文字(略語)を正しく処理する

実務では HTMLParserXMLHttpRequestgetAPIResponse のように連続する大文字(略語)を含む文字列を扱うことがあります。単純な /([A-Z])/g では各文字が分割されてしまいます。

JavaScript – 単純な方法の問題点
// 単純な方法 → 略語がバラバラになる
const bad = "HTMLParser".replace(/([A-Z])/g, ' $1').trim();
console.log(bad);
// "H T M L Parser"  ← 期待: "HTML Parser"

実行結果

"H T M L Parser"  ← 期待した結果ではない!

この問題を解決するには、2つのパターンを組み合わせた正規表現を使います。

JavaScript – 連続大文字を正しく処理
function splitByUpperCase(str) {
  return str
    // パターン1: 連続大文字の後に小文字が続く境界
    // "HTMLParser" → "HTML Parser"
    .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
    // パターン2: 小文字/数字の後に大文字が続く境界
    // "getAPI" → "get API"
    .replace(/([a-z0-9])([A-Z])/g, '$1 $2');
}

console.log(splitByUpperCase("HTMLParser"));
console.log(splitByUpperCase("XMLHttpRequest"));
console.log(splitByUpperCase("getAPIResponse"));
console.log(splitByUpperCase("iPhone12ProMax"));
console.log(splitByUpperCase("parseJSON2XML"));

実行結果

"HTML Parser"
"XML Http Request"
"get API Response"
"i Phone12 Pro Max"
"parse JSON2 XML"

2つの正規表現パターンの解説

パターン マッチ例 変換結果
/([A-Z]+)([A-Z][a-z])/g HTML + Parser HTML Parser
/([a-z0-9])([A-Z])/g get + API get API

ポイント:この「2段階 replace」パターンは、多くの OSS ライブラリ(lodash、change-case など)でも採用されている実績のあるアルゴリズムです。

数字を含む文字列の処理

実務では iPhone12ProMaxes2024Features のように数字を含む変数名も多くあります。数字とアルファベットの境界でも区切りたい場合は、パターンを追加します。

JavaScript – 数字を含む文字列の分割
function splitWords(str) {
  return str
    .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
    .replace(/([a-z])([A-Z])/g, '$1 $2')
    // 数字→文字 の境界
    .replace(/([0-9])([a-zA-Z])/g, '$1 $2')
    // 文字→数字 の境界
    .replace(/([a-zA-Z])([0-9])/g, '$1 $2');
}

console.log(splitWords("iPhone12ProMax"));
console.log(splitWords("es2024Features"));
console.log(splitWords("html5Canvas2D"));
console.log(splitWords("parseJSON2XML"));

実行結果

"i Phone 12 Pro Max"
"es 2024 Features"
"html 5 Canvas 2 D"
"parse JSON 2 XML"

snake_case / kebab-case への変換

スペース区切りの応用として、camelCase → snake_casecamelCase → kebab-case への変換もよく使われます。スペース区切りの結果をベースに、区切り文字を変えるだけで実現できます。

JavaScript – 各種ケース変換ユーティリティ
// 単語分割のベース関数
function toWords(str) {
  return str
    .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
    .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
    .replace(/[_\-]+/g, ' ')
    .trim()
    .split(/\s+/);
}

// camelCase → snake_case
function toSnakeCase(str) {
  return toWords(str).map(w => w.toLowerCase()).join('_');
}

// camelCase → kebab-case
function toKebabCase(str) {
  return toWords(str).map(w => w.toLowerCase()).join('-');
}

// camelCase → Title Case(先頭大文字スペース区切り)
function toTitleCase(str) {
  return toWords(str)
    .map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
    .join(' ');
}

// camelCase → CONSTANT_CASE
function toConstantCase(str) {
  return toWords(str).map(w => w.toUpperCase()).join('_');
}

const input = "backgroundColor";
console.log(toSnakeCase(input));
console.log(toKebabCase(input));
console.log(toTitleCase(input));
console.log(toConstantCase(input));

実行結果

"background_color"
"background-color"
"Background Color"
"BACKGROUND_COLOR"

逆変換 ― snake_case / kebab-case → camelCase

大文字でスペース区切りにする処理の逆、つまり snake_case や kebab-case を camelCase / PascalCase に戻す変換も覚えておくと便利です。

JavaScript – snake_case / kebab-case → camelCase
// snake_case / kebab-case → camelCase
function toCamelCase(str) {
  return str
    .toLowerCase()
    .replace(/[_\-\s]+(.)/g, (_, c) => c.toUpperCase());
}

// snake_case / kebab-case → PascalCase
function toPascalCase(str) {
  return str
    .toLowerCase()
    .replace(/(^|[_\-\s]+)(.)/g, (_, __, c) => c.toUpperCase());
}

console.log(toCamelCase("background_color"));
console.log(toCamelCase("font-size"));
console.log(toPascalCase("user_profile_settings"));
console.log(toPascalCase("my-component-name"));

実行結果

"backgroundColor"
"fontSize"
"UserProfileSettings"
"MyComponentName"

lodash(_.startCase / _.camelCase)との比較

ケース変換は lodashchange-case などのライブラリでも提供されています。自前実装とライブラリの違いを比較してみましょう。

JavaScript – lodash のケース変換メソッド
import _ from 'lodash';

// _.startCase: スペース区切り + 先頭大文字
_.startCase("backgroundColor");  // "Background Color"
_.startCase("HTMLParser");       // "HTML Parser"

// _.camelCase: camelCase に変換
_.camelCase("background-color"); // "backgroundColor"

// _.snakeCase: snake_case に変換
_.snakeCase("backgroundColor"); // "background_color"

// _.kebabCase: kebab-case に変換
_.kebabCase("backgroundColor"); // "background-color"

自前実装 vs ライブラリの比較

観点 自前実装 lodash change-case
バンドルサイズ 0 KB(追加なし) ~70 KB(全体)/ ~2 KB(個別) ~1 KB
連続大文字処理 自分で実装が必要 自動対応 自動対応
Unicode 対応 自分で拡張が必要 対応済み 対応済み
エッジケース テスト次第 豊富なテスト済み 豊富なテスト済み
カスタマイズ性 完全に自由 限定的 プラグイン可能
推奨シーン シンプルな変換 既に lodash 導入済み ケース変換が主目的

ポイント:シンプルな変換なら自前実装で十分です。プロジェクトに lodash が既に導入されている場合は、_.startCase() を使うのが最も手軽です。バンドルサイズを気にする場合は lodash/startCase で個別インポートしましょう。

実務での活用例

大文字でのスペース区切り・ケース変換は、フロントエンド・バックエンド問わず多くの場面で活用されます。代表的なユースケースを見てみましょう。

例1: オブジェクトのキーをUI表示用に変換

API から受け取った JSON のキー(camelCase)を、画面上ではスペース区切りのラベルとして表示するケースです。

JavaScript – APIキーをUI表示用に変換
const user = {
  firstName: "太郎",
  lastName: "山田",
  emailAddress: "taro@example.com",
  phoneNumber: "090-1234-5678",
  createdAt: "2024-01-15"
};

// キーをラベルに変換して表示
Object.entries(user).forEach(([key, value]) => {
  const label = key
    .replace(/([A-Z])/g, ' $1')
    .replace(/^./, c => c.toUpperCase());
  console.log(`${label}: ${value}`);
});

実行結果

First Name: 太郎
Last Name: 山田
Email Address: taro@example.com
Phone Number: 090-1234-5678
Created At: 2024-01-15

例2: React コンポーネント名からパンくずリストを生成

JavaScript – コンポーネント名からパンくず生成
function toBreadcrumb(componentName) {
  return componentName
    .replace(/([A-Z])/g, ' $1')
    .trim()
    .split(' ')
    .join(' > ');
}

console.log(toBreadcrumb("UserProfileSettings"));
console.log(toBreadcrumb("AdminDashboardAnalytics"));

実行結果

"User > Profile > Settings"
"Admin > Dashboard > Analytics"

例3: CSS プロパティ名の相互変換

JavaScript – CSS プロパティの camelCase ↔ kebab-case
// camelCase → CSS kebab-case
function cssProp(jsProp) {
  return jsProp.replace(/([A-Z])/g, '-$1').toLowerCase();
}

// CSS kebab-case → camelCase
function jsProp(cssPropName) {
  return cssPropName.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
}

console.log(cssProp("backgroundColor"));  // "background-color"
console.log(cssProp("borderTopWidth"));   // "border-top-width"
console.log(jsProp("margin-left"));      // "marginLeft"
console.log(jsProp("font-size"));        // "fontSize"

実行結果

"background-color"
"border-top-width"
"marginLeft"
"fontSize"

例4: テスト名の自動生成

JavaScript – 関数名からテストの説明文を生成
function describeFromName(fnName) {
  const words = fnName
    .replace(/([A-Z])/g, ' $1')
    .toLowerCase()
    .trim();
  return `should ${words}`;
}

console.log(describeFromName("validateEmail"));
console.log(describeFromName("calculateTotalPrice"));
console.log(describeFromName("fetchUserData"));

実行結果

"should validate email"
"should calculate total price"
"should fetch user data"

汎用ケース変換クラス ― まとめて使えるユーティリティ

ここまでのテクニックをまとめて、1つのクラスとして再利用可能な形にしておきましょう。プロジェクト全体で統一的にケース変換を行うのに便利です。

JavaScript – CaseConverter クラス
class CaseConverter {
  /** 文字列を単語配列に分解する(すべての変換の基礎) */
  static toWords(str) {
    return str
      .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
      .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
      .replace(/[_\-]+/g, ' ')
      .trim()
      .split(/\s+/)
      .filter(Boolean);
  }

  static toCamel(str) {
    const words = this.toWords(str);
    return words[0].toLowerCase() +
      words.slice(1).map(w =>
        w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()
      ).join('');
  }

  static toPascal(str) {
    return this.toWords(str)
      .map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
      .join('');
  }

  static toSnake(str) {
    return this.toWords(str).map(w => w.toLowerCase()).join('_');
  }

  static toKebab(str) {
    return this.toWords(str).map(w => w.toLowerCase()).join('-');
  }

  static toTitle(str) {
    return this.toWords(str)
      .map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
      .join(' ');
  }

  static toConstant(str) {
    return this.toWords(str).map(w => w.toUpperCase()).join('_');
  }
}

// 使用例
const input = "getUserAPIResponse";
console.log(CaseConverter.toWords(input));
console.log(CaseConverter.toSnake(input));
console.log(CaseConverter.toKebab(input));
console.log(CaseConverter.toTitle(input));
console.log(CaseConverter.toConstant(input));

実行結果

["get", "User", "API", "Response"]
"get_user_api_response"
"get-user-api-response"
"Get User Api Response"
"GET_USER_API_RESPONSE"

ブラウザ互換性

この記事で使用している JavaScript の機能について、ブラウザ対応状況を確認しておきましょう。

機能 Chrome Firefox Safari Edge IE
String.replace() 1+ 1+ 1+ 12+ 5.5+
正規表現(基本) 1+ 1+ 1+ 12+ 5.5+
後読み (?<=...) 62+ 78+ 16.4+ 79+ 非対応
アロー関数 45+ 22+ 10+ 12+ 非対応
テンプレートリテラル 41+ 34+ 9+ 12+ 非対応
class 構文 49+ 45+ 9+ 13+ 非対応

注意:後読み(lookbehind)(?<=...) は Safari 16.4 未満では使えません。古いブラウザをサポートする必要がある場合は、.replace(/([A-Z])/g, ' $1').trim() パターンを使いましょう。

よくある落とし穴と対処法

大文字でのスペース区切り処理で遭遇しやすいミスと、その対処法をまとめます。

落とし穴1: 先頭の余分なスペース

JavaScript – trim() を忘れた場合
// NG: trim() なし → 先頭にスペースが入る
const bad = "HelloWorld".replace(/([A-Z])/g, ' $1');
console.log(`"${bad}"`);  // " Hello World" ← 先頭にスペース!

// OK: trim() あり
const good = "HelloWorld".replace(/([A-Z])/g, ' $1').trim();
console.log(`"${good}"`); // "Hello World" ← 正しい

落とし穴2: 連続スペースの発生

JavaScript – 既にスペースを含む文字列の処理
// 入力に既にスペースやアンダースコアが含まれる場合
const mixed = "get_UserName";

// 対策: 区切り文字を統一してからスペース化
const result = mixed
  .replace(/[_\-]+/g, ' ')
  .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
  .replace(/\s+/g, ' ')  // 連続スペースを1つに
  .trim();

console.log(result); // "get User Name"

落とし穴3: 空文字列・null の処理

JavaScript – 安全な入力チェック
function safeSplitByUpperCase(str) {
  // null / undefined / 非文字列のガード
  if (typeof str !== 'string' || str.length === 0) {
    return '';
  }
  return str
    .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
    .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
    .trim();
}

console.log(safeSplitByUpperCase(""));          // ""
console.log(safeSplitByUpperCase(null));        // ""
console.log(safeSplitByUpperCase(undefined));   // ""
console.log(safeSplitByUpperCase("helloWorld")); // "hello World"

落とし穴4: g フラグの付け忘れ

JavaScript – g フラグの有無の違い
const str = "HelloWorldTest";

// NG: g フラグなし → 最初の1つしか置換されない
console.log(str.replace(/([A-Z])/, ' $1').trim());
// "Hello WorldTest" ← World と Test が分割されていない

// OK: g フラグあり → すべて置換される
console.log(str.replace(/([A-Z])/g, ' $1').trim());
// "Hello World Test" ← 正しい

ケース変換の早見表

各種ケース変換のパターンを一覧にまとめます。コピーして使ってください。

変換 入力例 出力例 コア処理
大文字スペース区切り helloWorld hello World replace(/([A-Z])/g, ' $1')
→ snake_case helloWorld hello_world 単語分割 + .join('_')
→ kebab-case helloWorld hello-world 単語分割 + .join('-')
→ Title Case helloWorld Hello World 単語分割 + 先頭大文字化
→ CONSTANT_CASE helloWorld HELLO_WORLD 単語分割 + 全大文字 + .join('_')
snake → camelCase hello_world helloWorld replace(/[_-]+(.)/, toUpper)
CSS camel → kebab backgroundColor background-color replace(/([A-Z])/g, '-$1').toLowerCase()

まとめ

JavaScriptでアルファベットの大文字ごとにスペースで区切る方法と、関連するケース変換テクニックを解説しました。

この記事のまとめ

  • replace(/([A-Z])/g, ' $1').trim() が最もシンプルな大文字スペース区切り
  • 連続大文字(略語)には 2段階 replace パターンを使う
  • snake_case / kebab-case への変換は、単語分割 + join() で実現
  • CSS プロパティの camelCase ↔ kebab-case 変換は実務で頻出
  • lodash / change-case はエッジケースに強いが、シンプルな変換なら自前で十分
  • .trim() の付け忘れと g フラグの付け忘れに注意