JavaScript | 文字列の長さを取得する(length プロパティ)

javascrpit JavaScript
スポンサーリンク

初心者向けにやさしく、実例たっぷりで噛み砕いて説明します。

ポイント → 「length文字コード単位(UTF-16のコードユニット) を数える」こと、「見た目の文字数(ユーザーが見る“文字”)」とは一致しない場合がある、そして実務でどう扱えばいいか、まで丁寧に示します。

基本の話:length で取れるもの

string.lengthその文字列を内部で表現している UTF-16 のコードユニットの個数 を返します。
普通の英数字や多くの日本語は「1文字 = 1コードユニット」なので直感どおりですが、絵文字などは例外があります。

console.log("Hello".length);     // 5
console.log("東京".length);      // 2
console.log("あ".length);        // 1
JavaScript

罠:サロゲートペア(絵文字など)

一部の文字(絵文字や一部の拡張漢字など)は UTF-16 で 2つのコードユニット に分かれて格納されます。length はそのまま「2」と数えます。

const s1 = "😊";        // にこちゃん絵文字
console.log(s1.length); // 2  ← 見た目は1文字なのに length は 2
JavaScript

つまり、length は**「見た目の文字数」ではなく内部単位数**を返します。

よくある必要性と対処法

  • 「入力を何文字まで許可する(例:ユーザー名は20文字まで)」
    → 絵文字を含むユーザー名だと length では不正確になる可能性あり。見た目の文字数で判定したい場合は別のカウントが必要。
  • 「文字列を 1 文字ずつ処理したい」
    for (let ch of str) やスプレッド・配列化でコードポイント単位(サロゲートペアを1つにまとめて扱える)でループできます。

コードポイント単位で扱う(サロゲートに対応)

const s = "a😊b";
console.log(s.length);           // 4('😊' が 2)
console.log([...s].length);      // 3(スプレッドはコードポイント単位で分割)
console.log(Array.from(s).length);// 3(同様)
JavaScript

でも — 「真の見た目の文字数(グリフ数)」はさらに別

合字(例:a + 結合振幅記号)や ZWJ(ゼロ幅結合子)で結合された複合絵文字(👩‍👩‍👧‍👦 のような家族絵文字)は、コードポイント数やスプレッドの個数とも一致しない場合があります。
「ユーザーが見る“1つの文字”」=グラフェムクラスタ(grapheme cluster) と呼ばれる概念で数えたい場合は専用ツールが必要です。

近年の良い方法:Intl.Segmenter

ブラウザの新しい機能 Intl.Segmenter を使うと、グラフェム単位で分割できます(ブラウザの対応状況に注意)。

function countGraphemes(str) {
  if (typeof Intl !== "undefined" && Intl.Segmenter) {
    const seg = new Intl.Segmenter(undefined, { granularity: "grapheme" });
    return [...seg.segment(str)].length;
  }
  // フォールバック:Array.from でコードポイント数(完璧ではない)
  return Array.from(str).length;
}

console.log(countGraphemes("a"));              // 1
console.log(countGraphemes("😊"));            // 1
console.log(countGraphemes("👩‍👩‍👧‍👦"));     // 1(Segmenter なら 1 と数える)
JavaScript

注意:Intl.Segmenter はかなり良いが、古いブラウザではサポートされていないことがあるので、フォールバック(Array.from など)を用意します。完全に正確にしたい場合は grapheme-splitter のようなライブラリ を使う選択肢もあります。

例題

以下は実際にブラウザのコンソールで試せる例です。

const list = [
  "hello",
  "東京",
  "😊",
  "a😊b",
  "e\u0301",        // e + combining acute accent(é を合成)
  "👩‍👩‍👧‍👦"         // ZWJ でつながった家族絵文字
];

for (const s of list) {
  console.log("文字列:", s);
  console.log(" length:", s.length);                 // UTF-16 コードユニット数
  console.log(" Array.from:", Array.from(s).length); // コードポイント単位(サロゲート対応)
  console.log(" [...seg]:", (Intl.Segmenter ? [...(new Intl.Segmenter(undefined,{granularity:"grapheme"})).segment(s)].length : "Segmenter未対応"));
  console.log("---");
}
JavaScript

期待される観察(環境による):

  • "hello" はすべて同じ(5)
  • "東京" は length 2、Array.from も 2
  • "😊" は length 2 だけど Array.from は 1
  • "a😊b" は length 4、Array.from は 3
  • e\u0301(e + 合成アクセント)は length 2、Array.from は 2、Segmenter は 1(見た目は é)
  • 👩‍👩‍👧‍👦 は length が複雑(複数コードユニット)、Array.from は複数、Segmenter は 1(家族絵文字1つ)

よくある実践的な戦略

  1. 単純表示・短い英数字のみlength で十分。
  2. 絵文字や多言語を含む入力で「見た目の文字数」をカウントしたいIntl.Segmenter を優先、未対応環境は Array.from をフォールバック。
  3. 完全な互換性が必要(サーバーサイド含む)grapheme-splitter などのライブラリを採用するのが安全。

サンプル関数:入力を「見た目の文字数」で判定して制限する

function visibleLength(str) {
  if (typeof Intl !== "undefined" && Intl.Segmenter) {
    const seg = new Intl.Segmenter(undefined, { granularity: "grapheme" });
    return [...seg.segment(str)].length;
  }
  return Array.from(str).length;
}

function isValidUserName(name, maxVisibleChars = 20) {
  return visibleLength(name) <= maxVisibleChars;
}

console.log(isValidUserName("Halu", 20));          // true
console.log(isValidUserName("😊😊😊😊😊", 4));       // false (5絵文字)
JavaScript

練習問題

  1. "a\u0301"a + combining acute accent)は lengthvisibleLength はそれぞれ何?
  2. "🇯🇵"(国旗絵文字)は lengthArray.fromvisibleLength はそれぞれ何?
  3. const s = "👩‍❤️‍💋‍👩"(キスするカップルの絵文字)を試して、3つの方法で数えて違いを観察せよ。

(答えは下にあります。自分で実行してから見ると学習効果大です)

答(ヒント付き)

  1. "a\u0301"
    • length → 2(a と combining mark)
    • Array.from → 2(code points)
    • visibleLength(Segmenterがあるなら) → 1(見た目は é)
  2. "🇯🇵"(国旗)
    • length → 4(国旗は2つの地域指示子で、それぞれがサロゲートペアだから合計4)
    • Array.from → 2(2つのコードポイント)
    • visibleLength → 1(見た目は1つの国旗)
  3. 👩‍❤️‍💋‍👩(ZWJで結合された複合絵文字)
    • length は大きな数(内部で複数のコードユニット)
    • Array.from は複数(複数の code points)
    • visibleLength(Segmenter) → 1(見た目は1つ)

まとめ(初心者向けの要点)

  • string.length内部のコードユニット数 を返す(直感通りの「文字数」ではないことがある)。
  • 絵文字や拡張文字を含む場合は length が見た目と一致しない。
  • サロゲートペア対応には Array.from やスプレッド [...str] が便利(コードポイント単位)。
  • 「本当に見た目の1文字単位で数える」なら Intl.Segmenter(または外部ライブラリ)を使うのが確実。
タイトルとURLをコピーしました