はい、承知いたしました。
JavaScriptのlength
プロパティについて、約5000語の詳細な解説記事を作成します。以下、記事です。
【JS】length
プロパティで文字列の文字数を取得する方法 – 基礎から応用、注意点まで徹底解説
はじめに
JavaScriptは、現代のWeb開発において欠かすことのできないプログラミング言語です。動的なウェブページの作成から、サーバーサイドアプリケーション(Node.js)、モバイルアプリ開発まで、その用途は多岐にわたります。そして、どのようなJavaScriptアプリケーションを開発するにせよ、必ずと言っていいほど扱うのが「文字列(String)」データです。
ユーザーからの入力を受け取ったり、画面にメッセージを表示したり、APIからデータを取得したりと、文字列操作はプログラミングの根幹をなす作業の一つです。その中でも、「文字列の長さを取得する」という操作は、最も基本的でありながら、非常に頻繁に利用されます。例えば、以下のような場面を想像してみてください。
- ユーザー登録フォームで、パスワードが指定された文字数以上であるかチェックする。
- SNSの投稿フォームで、入力された文字数をリアルタイムに表示し、上限を超えないように制御する。
- ブログ記事の要約を自動生成するために、本文の先頭から一定の文字数を切り出す。
- データが空でないことを確認するために、文字列の長さが0より大きいか判定する。
これらの処理を実現するためにJavaScriptが提供しているのが、length
プロパティです。一見すると、'hello'.length
と書けば5
が返ってくる、という単純な機能に見えるかもしれません。しかし、このlength
プロパティの背後には、JavaScriptの文字列がどのように扱われているかという、言語の深い仕組みが隠されています。
特に、絵文字や特殊な漢字など、現代の多様な文字を扱う際には、length
プロパティの挙動が私たちの直感と異なる結果を返すことがあります。この挙動を理解せずにいると、予期せぬバグや文字化けの原因となりかねません。
この記事では、JavaScriptのlength
プロパティについて、単なる使い方の紹介にとどまらず、その本質的な仕組みから注意点、そして実践的な応用例まで、徹底的に掘り下げて解説します。
この記事を通じて、あなたは以下のことを学べます:
length
プロパティの基本的な構文と使い方length
プロパティが返す値の正確な意味(「文字数」ではない?)- 絵文字などの
length
が1
にならない理由(サロゲートペアの謎) - 人間が認識する「本当の文字数」を取得するための現代的なテクニック
length
プロパティを効果的に活用する具体的なユースケースlength
プロパティにまつわる、よくある間違いや疑問の解決
この記事は、JavaScriptを学び始めた初心者の方から、すでにある程度の経験を持つ中級者の方まで、length
プロパティ、ひいてはJavaScriptの文字列操作への理解を一段階深めることを目指しています。それでは、length
プロパティの奥深い世界へ一緒に旅を始めましょう。
セクション1: length
プロパティの基本的な使い方
まずは基本から始めましょう。length
プロパティは、JavaScriptの文字列(String)オブジェクトが持つプロパティの一つで、その文字列の「長さ」を数値として返します。
構文
構文は非常にシンプルです。
javascript
string.length
文字列を表す変数や文字列リテラルの後ろにドット(.
)をつけ、続けてlength
と記述します。メソッドではないため、length()
のように括弧()
はつけません。これはよくある間違いなので注意してください。
具体的なコード例
実際にコードを見ながら、その挙動を確認していきましょう。
1. 文字列リテラルに直接使用する
文字列そのものに直接.length
をつけることができます。
javascript
// 'Hello, world!' という文字列の長さを取得
console.log('Hello, world!'.length);
// 出力: 13
// (H,e,l,l,o,,, ,w,o,r,l,d,! の13文字)
この例では、アルファベット、カンマ、スペース、感嘆符もすべて1文字としてカウントされ、合計13という結果が返ってきます。
2. 変数に格納した文字列に使用する
通常は、変数に格納された文字列に対してlength
プロパティを使用することが多いでしょう。
“`javascript
const myName = ‘JavaScript’;
const len = myName.length;
console.log('${myName}' の長さは ${len} です。
);
// 出力: ‘JavaScript’ の長さは 10 です。
“`
3. 空文字列の長さ
何もない空の文字列の長さはどうなるでしょうか。
javascript
const emptyString = '';
console.log(emptyString.length);
// 出力: 0
期待通り、0
が返されます。これは、文字列が空かどうかを判定する際によく利用されます。
4. 日本語の文字列
日本語のひらがな、カタカナ、漢字も問題なく扱えます。
javascript
const greeting = 'こんにちは、世界!';
console.log(greeting.length);
// 出力: 9
// (こ,ん,に,ち,は,、,世,界,! の9文字)
半角文字も全角文字も、この時点では区別なく1文字としてカウントされているように見えます。
length
プロパティは読み取り専用
重要な特性として、length
プロパティは読み取り専用(read-only)です。つまり、このプロパティに値を代入しようとしても、エラーにはなりませんが、文字列の内容や長さが変更されることはありません。
“`javascript
let str = ‘abc’;
console.log(str.length); // 出力: 3
// lengthプロパティに新しい値を代入しようと試みる
str.length = 10;
console.log(str); // 出力: ‘abc’ (変更されない)
console.log(str.length); // 出力: 3 (変更されない)
“`
これは、JavaScriptにおいて文字列がイミュータブル(immutable、不変)であるという性質に基づいています。一度作成された文字列オブジェクトの内容は、後から変更することができません。文字列を操作するメソッド(toUpperCase()
やslice()
など)は、元の文字列を変更するのではなく、新しい文字列を生成して返します。
この「読み取り専用」という性質は、配列のlength
プロパティとの大きな違いです。配列の場合、length
プロパティは書き込み可能で、値を変更すると配列の要素数が変化します。この違いはしっかりと覚えておきましょう。
ここまでがlength
プロパティの基本的な使い方です。非常に簡単で直感的に使えることがお分かりいただけたかと思います。しかし、冒頭で述べたように、この単純さの裏には注意すべき挙動が潜んでいます。次のセクションでは、length
プロパティが内部的に何を数えているのか、その仕組みに深く迫ります。
セクション2: length
プロパティの内部的な仕組み – UTF-16とサロゲートペア
length
プロパティが返す値は、私たちが直感的に考える「文字数」と必ずしも一致しない場合があります。この謎を解く鍵は、JavaScriptが文字列を内部でどのように表現しているかを理解することにあります。
JavaScriptの文字列とUnicode
コンピュータは「文字」を直接理解できません。すべてのデータは最終的に数値として扱われます。そこで、「どの数値がどの文字に対応するか」というルールを定めたものが文字コードです。現在、世界中のほとんどの言語の文字を網羅する標準的な文字コードとして Unicode が広く使われています。
JavaScriptの文字列も、このUnicodeに基づいて文字を扱います。具体的には、JavaScriptは文字列を UTF-16 (16-bit Unicode Transformation Format) という形式でエンコードされた、16ビット符号なし整数のシーケンス(連続した集まり)として内部的に保持しています。
ここが最も重要なポイントです。
length
プロパティが返すのは、厳密には「文字の数」ではなく、この「UTF-16のコードユニット(Code Unit)の数」なのです。
コードユニット (Code Unit) とは?
UTF-16におけるコードユニットとは、16ビット(2バイト)のデータ単位のことです。
Unicodeでは、世界中のすべての文字にコードポイント(Code Point)と呼ばれる一意の番号が割り振られています。例えば、'A'
はU+0041
、ひらがなの'あ'
はU+3042
といった具合です。(U+
はUnicodeのコードポイントであることを示す接頭辞です)
Unicodeのコードポイントの中でも、U+0000
からU+FFFF
までの範囲は基本多言語面(Basic Multilingual Plane, BMP)と呼ばれ、日常的に使われるほとんどの文字(アルファベット、数字、記号、ひらがな、カタカナ、常用漢字など)が含まれています。
BMP内の文字は、1つの16ビットコードユニットで表現できます。そのため、これらの文字だけで構成された文字列の場合、length
プロパティは私たちの直感通りの「文字数」を返します。
“`javascript
// ‘A’ (U+0041) は BMP 内
console.log(‘A’.length); // 出力: 1
// ‘あ’ (U+3042) は BMP 内
console.log(‘あ’.length); // 出力: 1
// ‘漢’ (U+6F22) は BMP 内
console.log(‘漢’.length); // 出力: 1
“`
サロゲートペア (Surrogate Pair) とは何か?
問題は、BMPに収まらない文字、つまりU+10000
以上のコードポイントを持つ文字を扱うときに発生します。これらの文字は追加面(Supplementary Planes)に属し、近年のUnicode規格で追加された絵文字、特殊な漢字(異体字など)、歴史的な文字などが含まれます。
16ビットで表現できる数値の範囲は0
から65535
(0xFFFF
)までです。U+10000
以上のコードポイントを持つ文字は、1つの16ビットコードユニットでは表現しきれません。
そこでUTF-16では、これらの文字を表現するためにサロゲートペアという仕組みを使います。これは、2つの16ビットコードユニットを組み合わせて、1つの文字を表現する手法です。
- 上位サロゲート(High Surrogate):
U+D800
からU+DBFF
の範囲のコードユニット - 下位サロゲート(Low Surrogate):
U+DC00
からU+DFFF
の範囲のコードユニット
この2つのコードユニットをペアで使うことで、BMP外の文字を表現します。
この仕組みのため、サロゲートペアで表現される1つの文字に対して、length
プロパティは2
を返します。なぜなら、length
はあくまでUTF-16コードユニットの数を数えているからです。
最も身近な例が絵文字です。
javascript
// '😂' (Face with Tears of Joy) - コードポイントは U+1F602
console.log('😂'.length);
// 出力: 2
この'😂'
という絵文字は、人間にとっては紛れもなく1文字ですが、JavaScriptのlength
プロパティは2
を返します。これは、'😂'
が内部的に2つのコードユニット(サロゲートペア)で構成されているためです。
同様に、BMP外の漢字でも同じ現象が起こります。
“`javascript
// ‘𠮟’ (JIS第3水準漢字) – コードポイントは U+20B9F
// 「叱」の異体字
console.log(‘𠮟’.length);
// 出力: 2
// ‘叱’ (常用漢字) – コードポイントは U+53F1 (BMP内)
console.log(‘叱’.length);
// 出力: 1
“`
なぜこのような複雑な仕組みに?
この一見すると不便な仕様は、歴史的な経緯によるものです。JavaScriptがNetscape社によって開発された1995年当時、Unicodeはまだ16ビット固定長のUCS-2という規格が主流でした。UCS-2ではすべての文字が16ビットで表現できると考えられていました。length
プロパティも、このUCS-2の文字数を返すシンプルな設計でした。
しかし、その後Unicodeが拡張され、16ビットの範囲を超える文字が追加されることになりました。後方互換性を保ちつつこれらの新しい文字に対応するため、JavaScript(およびJavaやWindowsなど)はUCS-2を拡張したUTF-16を採用しました。その結果、length
プロパティの「コードユニットの数を返す」という基本的な振る舞いは変わらないまま、サロゲートペアという概念が導入されたのです。
この挙動が引き起こす問題点
length
プロパティが直感的な文字数と異なる値を返すことは、具体的にどのような問題を引き起こすのでしょうか。
1. 文字数カウントの不一致
ユーザーに「140文字以内で入力してください」と伝えているのに、ユーザーが絵文字を1つ入力しただけでlength
が2増えてしまうと、ユーザーは混乱します。
2. 文字列操作での文字化け
より深刻なのは、文字列を操作する際にサロゲートペアが分断されてしまうリスクです。
例えば、文字列を反転させる処理を考えてみましょう。
“`javascript
function reverseString(str) {
return str.split(”).reverse().join(”);
}
const emojiStr = ‘abc😂def’;
console.log(emojiStr.length); // 8 (a,b,c, 上位サロゲート, 下位サロゲート, d,e,f)
const reversedStr = reverseString(emojiStr);
console.log(reversedStr); // “fed??cba” のように文字化けする (環境による)
“`
このコードでは、split('')
が文字列をUTF-16コードユニット単位でバラバラにしてしまうため、'😂'
を構成していたサロゲートペアが分断されます。そして、逆順に結合された結果、もはや有効な文字ではなくなり、�
(豆腐)のような文字化けとして表示されてしまうのです。
同様の問題は、substring()
やslice()
で文字列の一部を切り出す際にも発生する可能性があります。
“`javascript
const str = ‘你好😂’; // 長さは 3 (你, 好, サロゲートペア) ではない!
console.log(str.length); // 4
// 先頭3文字を切り出そうとすると…
const sliced = str.slice(0, 3);
console.log(sliced); // “你好?” のように文字化けする
“`
このように、length
プロパティの挙動とサロゲートペアの存在を理解することは、グローバルな環境で正しく動作するアプリケーションを開発するために不可欠です。次のセクションでは、この問題を克服し、「人間が見て認識する文字数」を正しく取得する方法について学びます。
セクション3: 「真の文字数」を取得する方法
length
プロパティがUTF-16コードユニットの数を返すことを理解した上で、私たちが本当に知りたい「見た目上の文字数」をカウントするにはどうすればよいでしょうか。幸いなことに、モダンなJavaScript(ES2015/ES6以降)には、この問題をエレガントに解決するための機能が備わっています。
方法1: Array.from()
またはスプレッド構文 (...
)
ES2015で導入されたこれらの機能は、サロゲートペアを正しく扱ってくれます。
文字列はイテラブル(iterable)なオブジェクトです。これは、for...of
ループなどでその要素を1つずつ取り出せるオブジェクトであることを意味します。そして、文字列のイテレータは、サロゲートペアを1つの文字として正しく認識するように設計されています。
Array.from()
メソッドやスプレッド構文は、このイテラブルの仕組みを利用して、文字列を「文字」の配列に変換します。
“`javascript
const strWithEmoji = ‘😂👨👩👧👦’;
// Array.from() を使う方法
const charsArray1 = Array.from(strWithEmoji);
console.log(charsArray1); // [ ‘😂’, ‘👨👩👧👦’ ]
console.log(charsArray1.length); // 出力: 2
// スプレッド構文 (…) を使う方法
const charsArray2 = […strWithEmoji];
console.log(charsArray2); // [ ‘😂’, ‘👨👩👧👦’ ]
console.log(charsArray2.length); // 出力: 2
// 比較: 従来の length プロパティ
console.log(strWithEmoji.length); // 出力: 13 (サロゲートペアや結合文字のコードユニットの合計)
“`
ご覧の通り、Array.from(str)
や[...str]
は、サロゲートペアで構成される絵文字'😂'
や、さらに複雑な家族の絵文字'👨👩👧👦'
を、それぞれ1つの要素として持つ配列を生成します。そして、その配列のlength
プロパティを取得することで、見た目上の文字数を正確にカウントできるのです。
この方法は非常にシンプルで可読性も高く、現在最も推奨される方法の一つです。
方法2: for...of
ループ
for...of
ループも、文字列のイテレータを利用するため、サロゲートペアを1文字として処理できます。文字数をカウントするだけであれば、カウンター変数を用意してループを回します。
“`javascript
const strWithEmoji = ‘a😂b👨👩👧👦c’;
let trueCharCount = 0;
for (const char of strWithEmoji) {
// このループでは char には ‘a’, ‘😂’, ‘b’, ‘👨👩👧👦’, ‘c’ が順番に入る
console.log(char);
trueCharCount++;
}
console.log(真の文字数: ${trueCharCount}
);
// 出力: 5
“`
この方法は、単に数えるだけでなく、各文字に対して何らかの処理を行いたい場合に特に有効です。
結合文字と書記素クラスタの問題
しかし、話はもう少し複雑です。Unicodeには結合文字(Combining Characters)というものが存在します。これは、先行する文字に付加されて、1つの文字を形成するものです。
例えば、'é'
という文字は、2通りの方法で表現できます。
1. 合成済み文字: U+00E9
(é
) という1つのコードポイント
2. 結合シーケンス: U+0065
(e
) + U+0301
(アキュートアクセント記号 ´
) という2つのコードポイント
“`javascript
const char1 = ‘é’; // 合成済み (U+00E9)
const char2 = ‘e\u0301’; // 結合シーケンス (e + ´)
console.log(char1: ${char1}, length: ${char1.length}
); // char1: é, length: 1
console.log(char2: ${char2}, length: ${char2.length}
); // char2: é, length: 2
console.log(char1 === char2); // false (コードポイントのシーケンスが異なる)
“`
見た目は同じ'é'
でも、length
は1
と2
で異なり、===
での比較もfalse
になります。さらに、Array.from()
やスプレッド構文も、この結合文字を分断してしまいます。
javascript
console.log([...char2]); // [ 'e', '́' ]
console.log([...char2].length); // 2
人間が認識する文字の単位は、このようなベース文字と結合文字のまとまりです。この単位を書記素クラスタ(Grapheme Cluster)と呼びます。私たちが本当に欲しい「真の文字数」とは、この書記素クラスタの数なのです。
家族の絵文字'👨👩👧👦'
も、実は'👨'
+ (ZWJ) + '👩'
+ (ZWJ) + '👧'
+ (ZWJ) + '👦'
という複数の絵文字をゼロ幅接合子(ZWJ)で連結した書記素クラスタです。Array.from()
や[...str]
がこれを1文字として扱えたのは、ES2015のイテレータが基本的な書記素クラスタに対応しているためですが、全てのケースで完璧ではありません。
方法3: Intl.Segmenter
(ES2022) – 最も正確な方法
この書記素クラスタの問題を、言語や文化的な背景を考慮して最も正確に解決するための最新のAPIが、ES2022で標準化されたIntl.Segmenter
です。
Intl.Segmenter
は、テキストを言語の規則に従って意味のあるセグメント(単語、文、そして書記素クラスタ)に分割するためのオブジェクトです。
“`javascript
const complexStr = ‘e\u0301नमस्ते👨👩👧👦’; // é + ナマステ(ヒンディー語) + 家族の絵文字
// 1. Segmenterインスタンスを作成
// ‘ja’ はロケール、granularity: ‘grapheme’ は書記素クラスタ単位で分割する指定
const segmenter = new Intl.Segmenter(‘ja’, { granularity: ‘grapheme’ });
// 2. 文字列をセグメントに分割
const segments = segmenter.segment(complexStr);
// 3. 分割されたセグメントを配列に変換して長さを取得
const graphemeCount = […segments].length;
console.log(graphemeCount);
// 出力: 3 ([‘é’], [‘नमस्ते’], [‘👨👩👧👦’] の3つの書記素クラスタ)
// 比較: 他の方法
console.log(complexStr.length); // 16
console.log([…complexStr].length); // 10 ([ ‘e’, ‘́’, ‘न’, ‘म’, ‘स’, ‘्’, ‘त’, ‘े’, ‘👨👩👧👦’ ])
“`
Intl.Segmenter
を使うと、'e'́
のような結合文字はもちろん、ヒンディー語の'नमस्ते'
のような複雑な連結をする文字も、家族の絵文字も、すべて正しく1つの書記素クラスタとして認識し、カウントできています。
この方法は最も堅牢ですが、比較的新しいAPIであるため、古いブラウザやNode.jsのバージョンではサポートされていない可能性があります。使用する環境の互換性を確認する必要がありますが、多言語対応や絵文字を厳密に扱う必要があるアプリケーションでは、最良の選択肢となります。
「真の文字数」取得方法のまとめ
方法 | 長所 | 短所 | おすすめの場面 |
---|---|---|---|
string.length |
高速、シンプル | サロゲートペアや結合文字を正しく扱えない | 内部的な処理、ASCII文字のみの場合 |
[...str].length |
シンプル、サロゲートペアを扱える | 結合文字は扱えない場合がある | ほとんどの日常的なユースケース、絵文字対応 |
Intl.Segmenter |
最も正確、書記素クラスタを完全にサポート | 比較的新しいAPI、少しコードが冗長 | 厳密な多言語対応、複雑な絵文字対応が必要な場合 |
セクション4: length
プロパティの応用例と実践的なユースケース
length
プロパティがUTF-16コードユニットを数えるという特性を理解した上で、このプロパティがどのような場面で有効に活用できるか、具体的なユースケースを見ていきましょう。
1. 入力フォームの文字数制限
Webフォームで最も一般的なユースケースの一つです。ユーザーが入力できる文字数を制限し、リアルタイムでフィードバックを提供します。
“`html
残り文字数: 140
``
length
ここでプロパティを使うことには、一つ興味深い点があります。Twitter(現X)のような多くの主要なWebサービスでは、文字数カウントに
length`プロパティ(つまりUTF-16コードユニット)を用いています。これは、絵文字を入力すると2文字分としてカウントされることを意味します。
なぜ見た目上の文字数ではなく、コードユニットでカウントするのでしょうか? 一つの理由として、データベースに保存する際のバイト数制限が考えられます。多くのデータベースシステムでは、文字列を格納するカラムにバイト単位で最大長が設定されています(例: VARCHAR(255)
)。UTF-16のコードユニット数は、ストレージサイズとある程度の相関があるため、技術的な制約からこの方法が採用されている場合があります。
2. 文字列が空かどうかの判定
文字列の変数が空文字列(''
)かどうかを判定するのは非常に一般的な操作です。
```javascript
let userInput = ''; // ユーザー入力を想定
// 方法1: length を使う (推奨)
if (userInput.length === 0) {
console.log('入力が空です。');
}
// 方法2: 空文字列リテラルと比較する
if (userInput === '') {
console.log('入力が空です。');
}
```
どちらも機能的には同じですが、!str.length
のような短縮形もよく使われます。0
はfalsy(偽と評価される値)であるため、length
が0
の場合に!0
がtrue
になることを利用したイディオムです。
javascript
if (!userInput.length) {
console.log('入力が空です。');
}
これは簡潔ですが、「長さがない場合」という意図がlength === 0
よりも少し分かりにくいと感じる人もいるかもしれません。チームのコーディング規約に合わせて選択すると良いでしょう。
3. ループ処理での利用
古典的なfor
ループで、文字列内の各文字にアクセスするためにlength
が使われます。
javascript
const message = 'Hello';
for (let i = 0; i < message.length; i++) {
// i は 0, 1, 2, 3, 4 と変化
console.log(`インデックス ${i}: ${message[i]}`);
}
出力:
インデックス 0: H
インデックス 1: e
インデックス 2: l
インデックス 3: l
インデックス 4: o
この方法は、インデックス番号が必要な場合に便利です。ただし、セクション2で警告したように、この方法はサロゲートペアを分断するリスクがあります。 絵文字などを含む可能性のある文字列を1文字ずつ安全に処理したい場合は、for...of
ループを使うべきです。
4. 文字列の末尾の文字を取得
length
は0から始まるインデックスと組み合わせて、最後の文字にアクセスするためによく使われます。文字列の最後の文字のインデックスはlength - 1
になります。
```javascript
const filename = 'document.pdf';
const lastChar = filename[filename.length - 1];
console.log(lastChar); // 'f'
``
at()`メソッドを使うと、より直感的に書くことができます。
これはシンプルで効果的な方法ですが、ES2022で導入された
javascript
const lastCharWithAt = filename.at(-1);
console.log(lastCharWithAt); // 'f'
at(-1)
は末尾から1番目の要素を取得することを意味し、可読性が向上します。
5. 文字列の切り捨て(Truncation)
ブログのプレビューや長いメッセージの表示などで、文字列を一定の長さに丸めて「...」をつける処理です。
```javascript
function truncate(str, maxLength) {
if (str.length <= maxLength) {
return str;
}
return str.slice(0, maxLength) + '...';
}
const longText = 'JavaScriptは非常に柔軟で強力な言語です。';
console.log(truncate(longText, 10)); // 'JavaScript...'
``
maxLength`がちょうどサロゲートペアの真ん中だった場合、文字化けを引き起こします。
ここでもサロゲートペアの問題が潜んでいます。
javascript
const emojiText = 'これは😂とても長い文章です。';
console.log(truncate(emojiText, 4)); // "これは�..." のように文字化けする可能性
安全に切り捨てるには、セクション3で紹介したArray.from()
などを組み合わせる必要があります。
```javascript
function safeTruncate(str, maxGraphemes) {
const graphemes = Array.from(str);
if (graphemes.length <= maxGraphemes) {
return str;
}
return graphemes.slice(0, maxGraphemes).join('') + '...';
}
console.log(safeTruncate(emojiText, 4)); // 'これは😂...'
``
length`の特性を理解することで、ユースケースに応じて適切な処理を選択し、潜在的なバグを未然に防ぐことができます。
このように、
6. パフォーマンスに関する考察
length
プロパティへのアクセスは非常に高速です。これはO(1)操作であり、文字列の長さに関わらず、ほぼ一定の時間で値を取得できます。JavaScriptエンジンは、文字列が作成または変更された時点でその長さ(コードユニット数)を計算し、内部的に保持しています。そのため、length
プロパティにアクセスするたびに、文字列の最初から最後までスキャンするようなコストのかかる処理は行われません。安心して頻繁に利用できるプロパティです。
セクション5: length
プロパティに関するよくある質問と間違い (FAQ)
ここでは、length
プロパティを扱う上で初心者がつまずきやすい点や、よくある疑問についてQ&A形式で解説します。
Q1: string.length
と string.length()
の違いは何ですか?
A: length
はプロパティであり、メソッドではありません。したがって、string.length
のように括弧なしでアクセスします。string.length()
のように括弧をつけて呼び出そうとすると、TypeError: string.length is not a function
というエラーが発生します。
これは、JavaのString.length()
やPHPのstrlen()
のように、他の言語では文字列の長さを取得するのが関数やメソッドであることが多いため、混同しやすいポイントです。JavaScriptではプロパティであると覚えておきましょう。
Q2: なぜ絵文字のlength
は1にならないのですか?
A: これはセクション2で詳しく解説した通り、JavaScriptのlength
プロパティが数えているのが、見た目の「文字数」ではなく、内部表現である「UTF-16コードユニットの数」だからです。
多くの絵文字や一部の特殊な漢字は、16ビットの範囲を超えるUnicodeコードポイントを持っており、それらを表現するためにサロゲートペアと呼ばれる2つのコードユニットの組み合わせが使われます。そのため、length
プロパティは2
を返します。
Q3: null
やundefined
のlength
を取得しようとするとどうなりますか?
A: エラーになります。具体的にはTypeError: Cannot read properties of null (reading 'length')
やTypeError: Cannot read properties of undefined (reading 'length')
というエラーが発生します。
null
やundefined
は文字列オブジェクトではないため、length
プロパティを持っていません。存在しないプロパティにアクセスしようとした結果、エラーがスローされます。
これを防ぐためには、length
にアクセスする前に変数の存在をチェックするのが定石です。
```javascript
let myVar = null; // APIからのレスポンスなどが想定される
// 安全なチェック方法
if (myVar && myVar.length > 0) {
console.log('変数には空でない文字列が入っています。');
} else {
console.log('変数は null, undefined, または空文字列です。');
}
```
Q4: 配列のlength
プロパティとの違いは何ですか?
A: 2つの大きな違いがあります。
-
書き込み可能性:
- 文字列の
length
は読み取り専用です。 値を代入しても無視されます。 - 配列の
length
は書き込み可能です。 値を変更すると、配列の要素数が実際に変わります。より小さい値を設定すると末尾の要素が切り捨てられ、より大きい値を設定すると空のスロットが作られます。
javascript
let arr = [1, 2, 3, 4];
console.log(arr.length); // 4
arr.length = 2;
console.log(arr); // [1, 2] - 文字列の
-
意味合い:
- 文字列の
length
は「UTF-16コードユニットの数」です。 - 配列の
length
は「要素の数」です。
- 文字列の
Q5: length
とbyteLength
(バイト長)の違いは何ですか?
A: length
は、前述の通りUTF-16コードユニットの数です。一方、バイト長は、文字列を特定のエンコーディング(UTF-8, Shift_JISなど)でバイト列に変換したときの、そのバイトの数を指します。
length
とバイト長は通常一致しません。例えば、UTF-8エンコーディングでは、ASCII文字は1バイトですが、多くの日本語文字は3バイト、絵文字は4バイトになります。
JavaScriptで文字列のUTF-8でのバイト長を取得するには、TextEncoder
APIを使用します。
```javascript
const str = 'あ😂';
console.log('length (コードユニット数):', str.length); // 3 (あ:1, 😂:2)
const encoder = new TextEncoder();
const utf8Bytes = encoder.encode(str);
console.log('byteLength (UTF-8バイト数):', utf8Bytes.length); // 7 (あ:3バイト, 😂:4バイト)
```
サーバーとの通信でデータサイズを扱う際などには、このバイト長の概念が重要になります。
Q6: 全角文字と半角文字は同じ「1」としてカウントされますか?
A: length
プロパティの観点からは、ほとんどの場合、答えは「はい」です。
半角のアルファベットや数字(例: A
, 1
)も、全角のひらがな、カタカナ、常用漢字(例: あ
, ア
, 漢
)も、そのほとんどがUnicodeの基本多言語面(BMP)内に収まっています。そのため、これらはすべて1つのUTF-16コードユニットで表現され、length
は1
になります。
length
プロパティは、文字の表示上の幅(全角/半角)を区別しません。ただし、セクション2で触れた'𠮟'
のような一部の特殊な漢字はBMP外にあるため、length
が2
になる例外も存在します。
まとめ
この記事では、JavaScriptのlength
プロパティについて、その基本的な使い方から、私たちの直感を裏切ることのある内部的な挙動、そして現代的なWeb開発における実践的な対処法まで、包括的に解説してきました。
最後に、重要なポイントを振り返りましょう。
length
プロパティは手軽で高速: 文字列のUTF-16コードユニットの数を瞬時に返してくれる、非常に便利なプロパティです。length
は「文字数」ではない:length
が返すのは、厳密には「見た目の文字数」ではなく「UTF-16コードユニット数」です。この違いが、特に絵文字や一部の特殊文字を扱う際に重要になります。- サロゲートペアを理解する:
U+10000
以上のコードポイントを持つ文字は、2つのコードユニット(サロゲートペア)で表現されるため、length
は2
を返します。この挙動を知らないと、文字数カウントや文字列操作でバグを生む原因となります。 - 「真の文字数」が必要な場合の解決策:
[...str].length
やArray.from(str).length
: サロゲートペアを正しく1文字として扱える、シンプルで強力な方法です。多くのケースでこれで十分です。Intl.Segmenter
: 結合文字なども含めた「書記素クラスタ」を最も正確にカウントできる最新のAPIです。厳密な多言語対応が求められる場面での最終兵器です。
- ユースケースに応じた使い分けが鍵:
length
の特性を理解し、単純なチェックにはlength
を、ユーザーに見せる文字数や安全な文字列操作には[...str].length
やIntl.Segmenter
をと、目的に応じて最適なツールを選択することが、堅牢で信頼性の高いコードを書くための秘訣です。
JavaScriptのlength
プロパティは、言語の表面的な機能と、その背後にあるUnicodeやエンコーディングといったコンピュータサイエンスの深い領域とを結びつける、興味深い一例です。言語仕様の歴史的背景を知り、一見単純なプロパティの挙動を深く理解することは、私たちプログラマーの技術的な視野を広げ、より良いソフトウェアを生み出すための力となるでしょう。
これからもJavaScriptの学習を進める上で、この記事があなたの助けとなれば幸いです。