kubell Creator's Note

株式会社kubellのエンジニアのブログです。

ビジネスチャット「Chatwork」のエンジニアのブログです。

読者になる

JavaScriptで文字列の長さを取得するときの差を言語仕様から深掘りしてみる

この記事はkubell Advent Calendar 2025の9日目の記事です。

こんにちは、kubellのフロントエンド開発グループの末竹(magcho)🐧です。

最近はJavaScriptの言語仕様を読むことを趣味としています。この趣味ですがなかなか体感したことのない方には伝えづらく今回ブログに書き起こすことにしました。

ユーザーからの入力を受け付けるようなアプリケーションを作っていると、文字数に上限を設定したくなる場面が多々あると思います。 JavaScriptで文字数を数える方法としていくつかありますが、String.lengthは絵文字など一部の文字を正しく数え上げることができないためスプレッド演算子を使うことを紹介する記事が見つかります。 今回はこれらの挙動の違いがどこから来るものなのかを追いかけてみます。

挙動

方法 数え方 結果
String.length "あいうえお🐧".length 7
スプレッド演算子 [..."あいうえお🐧"].length 6

String.lengthの仕様

String.lengthの言語仕様によると

The number of elements in the String value represented by this String object.

随分とあっさりしている記述ですが、言語仕様上のString typeの扱いは別途 https://tc39.es/ecma262/multipageecmascript-data-types-and-values.html#sec-ecmascript-language-types-string-type に記述されていて

The String type is the set of all ordered sequences of zero or more 16-bit unsigned integer values (“elements”) up to a maximum length of 2**53 - 1 elements.

ECMAScript operations that do not interpret String contents apply no further semantics. Operations that do interpret String values treat each element as a single UTF-16 code unit.

との記述なので内部的には全てUTF-16として解釈しているようです。

UTF-16

挙動で確認したように あいうえお という文字列は5文字として数えることができていますが 🐧 (ペンギンの絵文字)は2文字として数えられています。 ペンギンの絵文字のコードポイントは U+1F427 です。これをJavaScriptの内部処理上の表現であるUTF-16で表現すると 0xD83D 0xDC27 となり、コードユニットが2つ分になります。

UTF-16表現においては2文字と計算される表現であることがわかりました。

JSにはコードポイントとString Objectを相互変換する仕組みがあり、これで確かめてみるとペンギンの絵文字は2文字として数えられていることがわかります。

"🐧".codePointAt(0).toString(16)
// "1f427"

"🐧" === String.fromCharCode(0xD83D, 0xDC27)
// true

ここまでString.lengthが🐧を見た目上の文字数と異なる理由を追いかけました。

スプレッド演算子の仕様

スプレッド演算子は...の右項が反復可能な値である時に展開をします。 Iteration_protocols によると反復可能な値は以下の条件を満たす必要があるそうです。

反復可能であるために、オブジェクトは [Symbol.iterator]() メソッドを実装する必要があります。これはつまり、オブジェクト(または、プロトタイプチェーン上のオブジェクトの一つ)が [Symbol.iterator] キー(Symbol.iterator 定数にて利用可)のプロパティを持つ必要があります。

[Symbol.iterator]()イテレータープロトコルに準拠するオブジェクトを返す、引数なしの関数。

今回確認したいのはString Objectのスプレッド演算子の挙動なので確認すべき項目は String.prototype[Symbol.iterator] の振る舞いということがわかりました。

String.prototype[Symbol.iterator]の仕様

言語仕様をみてみると。

ecma262のsec-string.prototype-%symbol.iterator%の仕様のスクリーンショット

4.c.ⅱ にてコードユニットのサイズを考慮した状態で文字を1つずつ取り出すイテレータであることが定義されています。この考慮により🐧は0xD83D0xDC27の2文字ではなく1文字として取り出されていることがわかります。

このことから上記2つの方法で文字列長を数えたときに挙動の差ができることがわかりました。

まとめ

今回は🐧(ペンギンの絵文字)を中心としてString.length[...String].lengthの違いについて深掘りをして、JavaScript処理系の文字列内部表現がUTF-16であり、文字列長の取得方法により数え方が異なる仕様となっていることがわかりました。

私が言語仕様を読むときの頭の中をそのままブログに起こしていったのであまりまとまりのない文章になっていますが、言語仕様をまだ読み解いたことのない方に向けて情報収集の順番などが伝われば幸いです。

執筆にあたってサロゲートペアやIntl.Segmenterも調べていたのですが文量が多くなりそうだったので別記事にします、次回作にご期待ください。

また、仕様を読むための前知識としてsyumaiさんの ECMAScript仕様の読み方ガイド 〜比較演算子編〜、矢野啓介さんの文字処理についてプログラマのための文字コード技術入門がとても参考になりました(bow)

明日は同じフロントエンド開発グループで一緒に開発をしているのishida-0622さんです。