【JavaScript】文字列を逆順に並び替える方法|スプレッド構文で絵文字も壊さない反転と回文判定まで解説

JavaScriptで「文字列を逆さまにしたい(末尾から先頭の順に並べ替えたい)」という場面があります。回文(パリンドローム)判定や文字列処理の練習問題でよく登場します。

定番は split("").reverse().join("") ですが、この書き方は絵文字や一部の漢字(サロゲートペア)を壊してしまうという大きな落とし穴があります。この記事では安全な書き方を主役に、各手法の比較、絵文字・結合文字への対応、回文判定まで解説します。

この記事の結論:反転は [...str].reverse().join("") を使いましょう。スプレッド構文 [...str] はコードポイント単位で分割するため、split("") と違って絵文字(サロゲートペア)を壊しません
スポンサーリンク

スプレッド構文で反転する(推奨・絵文字も安全)

[...str] で文字(コードポイント)単位の配列にし、reverse() で並べ替えて join("") で文字列に戻します。

スプレッド構文で反転
function reverseString(str) {
  return [...str].reverse().join("");
}

console.log(reverseString("Hello")); // "olleH"
console.log(reverseString("こんにちは")); // "はちにんこ"

split(“”).reverse().join(“”) で反転する(古典)

昔からよく使われる書き方です。半角英数字だけなら問題なく動きますが、絵文字やサロゲートペアを含むと壊れるため、現在は [...str] 版が推奨です。

split() で反転(ASCII向け)
function reverseString(str) {
  return str.split("").reverse().join("");
}

console.log(reverseString("Hello")); // "olleH"(英数字ならOK)

for ループ・再帰で反転する

ライブラリに頼らず手続き的に書く方法です。学習用途で見かけます。

for ループ
function reverseString(str) {
  let result = "";
  for (let i = str.length - 1; i >= 0; i--) {
    result += str[i];
  }
  return result;
}

console.log(reverseString("Hello")); // "olleH"
再帰関数
function reverseString(str) {
  if (str === "") return "";
  // substr は非推奨なので slice を使う
  return reverseString(str.slice(1)) + str.charAt(0);
}

console.log(reverseString("Hello")); // "olleH"
for ループ・再帰も str[i] / charAt() はコード単位なので、絵文字を含むと壊れます。また再帰は非常に長い文字列でスタックオーバーフローになる可能性があるため、実務では [...str].reverse().join("") が安全で簡潔です。

【重要】絵文字・サロゲートペアの罠

絵文字(😀 など)はUTF-16で2つのコード単位(サロゲートペア)として表されます。split("") はこれを2つに分割してしまうため、反転すると順序が崩れて文字化けします。[...str] ならコードポイント単位なので壊れません。

split は壊れる / スプレッドは安全
const str = "a\u{1F600}b"; // 真ん中が絵文字(\u{1F600})の文字列

// NG: split("") は絵文字を2つに割ってしまう → 反転で文字化け
console.log(str.split("").reverse().join("")); // "b" + 壊れた絵文字 + "a"

// OK: スプレッド構文ならコードポイント単位で安全
console.log([...str].reverse().join("")); // "b" + 絵文字 + "a"(正しく反転)

結合文字を含む場合は Intl.Segmenter

「é」のように基底文字+結合文字e + 結合アクセント)で表される文字は、[...str](コードポイント単位)でも分解されてしまい、反転するとアクセントの位置が崩れます。見た目の1文字(書記素クラスター)単位で扱うには Intl.Segmenter を使います。

Intl.Segmenter で書記素単位に反転
function reverseByGrapheme(str) {
  const seg = new Intl.Segmenter("ja", { granularity: "grapheme" });
  return [...seg.segment(str)].map(s => s.segment).reverse().join("");
}

// "cafe" + 結合アクセント(\u0301) = 見た目は "café"
const str = "cafe\u0301";

console.log([...str].reverse().join(""));   // アクセントが外れて崩れる
console.log(reverseByGrapheme(str));        // "éfac"(正しく反転)
Intl.Segmenter は比較的新しいAPIです(Chrome/Edge 87+、Safari 14.1+、Firefox 125+、Node.js 16+)。結合文字や絵文字シーケンス(家族絵文字など)まで厳密に扱いたいときに使い、通常のテキストは [...str].reverse().join("") で十分です。

実用例:回文(パリンドローム)判定

反転した文字列と元の文字列を比較すれば、回文かどうかを判定できます。

回文判定
function isPalindrome(str) {
  const s = str.toLowerCase();
  return s === [...s].reverse().join("");
}

console.log(isPalindrome("level"));         // true
console.log(isPalindrome("Hello"));         // false
console.log(isPalindrome("たけやぶやけた")); // true

方法の使い分け

方法 絵文字対応 備考
[...str].reverse().join("") 推奨。簡潔でサロゲートペア安全
Array.from(str).reverse().join("") スプレッドと同等
str.split("").reverse().join("") × 古典。英数字のみなら可
for ループ / 再帰 × 学習用。再帰はスタック制限に注意
Intl.Segmenter 結合文字・絵文字シーケンスまで厳密

よくある質問(FAQ)

Q文字列を逆順にする一番簡単な方法は?
A[...str].reverse().join("") です。文字列をスプレッド構文でコードポイント単位の配列にし、reverse() で反転して join("") で結合します。Array.from(str).reverse().join("") も同じ結果になります。
Qなぜ split(“”).reverse().join(“”) は避けるべきなのですか?
Asplit("") はUTF-16のコード単位で分割するため、絵文字や一部の漢字(サロゲートペア)を2つの不正な片割れに割ってしまいます。反転すると順序が逆になって文字化けします。[...str] はコードポイント単位なのでこの問題が起きません。
Q回文(パリンドローム)かどうかを判定するには?
A元の文字列と反転した文字列を比較します:s === [...s].reverse().join("")。大文字小文字を無視するなら toLowerCase() で揃え、空白や記号を無視するなら事前に除去してから比較します。
Q「é」のような文字が反転で崩れます。
A基底文字+結合文字(e + 結合アクセント)で表される文字は、コードポイント単位だと分解されてしまいます。Intl.Segmenter で書記素クラスター単位に分割してから反転すると崩れません。あるいは str.normalize("NFC") で合成済みの形に正規化してから扱う方法もあります。

まとめ

JavaScriptで文字列を逆順に並び替える方法のポイントを整理します。

  • 反転は [...str].reverse().join("") が基本(簡潔・サロゲートペア安全)
  • split("").reverse().join("") は絵文字を壊すので避ける
  • for ループ・再帰も str[i] はコード単位なので絵文字に非対応(再帰はスタック制限も)
  • 結合文字・絵文字シーケンスまで厳密に扱うなら Intl.Segmenter
  • 回文判定は s === [...s].reverse().join("")

関連として、文字を1文字ずつ取得する方法サロゲートペアへの対応もあわせて確認すると、文字列を安全に扱う力が身につきます。