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] 版が推奨です。
function reverseString(str) {
return str.split("").reverse().join("");
}
console.log(reverseString("Hello")); // "olleH"(英数字ならOK)
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"
str[i] / charAt() はコード単位なので、絵文字を含むと壊れます。また再帰は非常に長い文字列でスタックオーバーフローになる可能性があるため、実務では [...str].reverse().join("") が安全で簡潔です。【重要】絵文字・サロゲートペアの罠
絵文字(😀 など)はUTF-16で2つのコード単位(サロゲートペア)として表されます。split("") はこれを2つに分割してしまうため、反転すると順序が崩れて文字化けします。[...str] ならコードポイント単位なので壊れません。
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 を使います。
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)
[...str].reverse().join("") です。文字列をスプレッド構文でコードポイント単位の配列にし、reverse() で反転して join("") で結合します。Array.from(str).reverse().join("") も同じ結果になります。split("") はUTF-16のコード単位で分割するため、絵文字や一部の漢字(サロゲートペア)を2つの不正な片割れに割ってしまいます。反転すると順序が逆になって文字化けします。[...str] はコードポイント単位なのでこの問題が起きません。s === [...s].reverse().join("")。大文字小文字を無視するなら toLowerCase() で揃え、空白や記号を無視するなら事前に除去してから比較します。e + 結合アクセント)で表される文字は、コードポイント単位だと分解されてしまいます。Intl.Segmenter で書記素クラスター単位に分割してから反転すると崩れません。あるいは str.normalize("NFC") で合成済みの形に正規化してから扱う方法もあります。まとめ
JavaScriptで文字列を逆順に並び替える方法のポイントを整理します。
- 反転は
[...str].reverse().join("")が基本(簡潔・サロゲートペア安全) split("").reverse().join("")は絵文字を壊すので避ける- for ループ・再帰も
str[i]はコード単位なので絵文字に非対応(再帰はスタック制限も) - 結合文字・絵文字シーケンスまで厳密に扱うなら
Intl.Segmenter - 回文判定は
s === [...s].reverse().join("")
関連として、文字を1文字ずつ取得する方法やサロゲートペアへの対応もあわせて確認すると、文字列を安全に扱う力が身につきます。

