String.prototype.trim() は何をトリムするのか?
@okunokentaro さんが似た内容で先にスクラップを投稿されており、本記事の執筆時期と内容が重なってしまいました。こちらでは ECMAScript に加え、Java での調査結果なども含まれています。併せてご参考ください!
https://zenn.dev/okunokentaro/scraps/256c7d9a56ac69(本記事の公開はご本人にも確認を取っております)
String.prototype.trim()
JavaScript でコードを書いていて、とある文字列の端から空白を削除したくなったらどうしますか?
多くの人は String.prototype.trim()
を使うかと思います。
では、ここで削除される "空白" は何を指すか知っているでしょうか?
恥ずかしながら、私は正確には把握しておらず、「半角・全角スペースとか改行、タブあたりをいい感じに消してくれる良いヤツ」ぐらいの認識でした。
そんななか、業務においてこの "空白" の仕様を正確に把握しなければいけないシチュエーションが発生し調べたので、記事として残しておきます。
MDN 上の記述
まずは、MDN で String.prototype.trim()
のページを確認してみます。
trim()
メソッドは、文字列の両端の空白を削除します。このコンテクストでの空白には、空白文字(スペースやタブ、ノーブレークスペースなど)とすべての改行文字(LF や CR など)を含みます。
- 空白文字(スペースやタブ、ノーブレークスペースなど)
- すべての改行文字(LF や CR など)
が削除対象とのことですが、ノーブレークスペース "など" と記述があるように、一部の仕様については省略されているようです。
もう少し正確な仕様を知りたいので、ECMAScript の spec を追ってみましょう。
ECMA-262 上の記述
String.prototype.trim()
の仕様を確認します。
文字列のトリム部分については TrimString
を見ると、次のような説明があります。
The definition of white space is the union of WhiteSpace and LineTerminator. When determining whether a Unicode code point is in Unicode general category “Space_Separator” (“Zs”), code unit sequences are interpreted as UTF-16 encoded code point sequences as specified in 6.1.4.
空白 (white space) の定義は WhiteSpace
と LineTerminator
を見ると確認できそうです。また、Unicode 内の Space_Separator
に含まれるかもチェックされるようです。(Space_Separator
については後述)
WhiteSpace
WhiteSpace
の定義を確認すると、該当する文字コードが記載されています。
Code Point | Name |
---|---|
U+0009 |
CHARACTER TABULATION <TAB> : 水平タブ |
U+000B |
LINE TABULATION <VT> : 垂直タブ |
U+000C |
FORM FEED (FF) <FF> : 改ページ |
U+FEFF |
ZERO WIDTH NO-BREAK SPACE <ZWNBSP> : ゼロ幅スペース |
具体的が文字が出てきましたね。少なくともこの4つの文字は削除対象であることがわかりました。
しかし、これらに加えて次のような記述も存在します。
any code point in general category “Space_Separator”
Space_Separator
とは、一体なんでしょうか?
Unicode / General Category
Unicode では、文字・数字・句読点・記号などの文字の特性に応じて General Category (カテゴリ) を割り当てて分類しています。Space_Separator
もこの General Category のひとつで、空白に関する文字群が割り当てられています。
なお、General Category の分類には 2 文字の別名も存在し、Space_Separator
の場合は "Zs"
が該当します。
General Category についての詳細は、Unicode 仕様の "4.5 General Category" に記載があり、
その中でも Zs = Separator, space
の記述を確認できます。
Zs = Space_Separator に含まれる文字
では、Space_Separator
に含まれる文字は何でしょうか。
unicode.org では、Unicode の文字情報の検索用に Unicode Utilities というページが公開されており、そこから Character Property Index
を辿ることで General Category ごとの文字を確認することができます。
最終的には次のページに到達し、Space_Separator
に含まれる文字を確認できます。
というわけで、得られた空白文字の一覧がこちらです。
Code Point | Name |
---|---|
U+0020 |
SPACE : 半角スペース |
U+00A0 |
NO-BREAK SPACE :  
|
U+1680 |
OGHAM SPACE MARK : オガム文字のスペース |
U+2000 |
EN QUAD : 1en の幅のスペース |
U+2001 |
EM QUAD : 1em の幅のスペース |
U+2002 |
EN SPACE : 1en の幅のスペース |
U+2003 |
EM SPACE : 1em の幅のスペース |
U+2004 |
THREE-PER-EM SPACE : 1em の 1/3 のスペース |
U+2005 |
FOUR-PER-EM SPACE : 1em の 1/4 のスペース |
U+2006 |
SIX-PER-EM SPACE : 1em の 1/6 のスペース |
U+2007 |
FIGURE SPACE : 数字と同じ幅のスペース |
U+2008 |
PUNCTUATION SPACE : ピリオドと同じ幅のスペース |
U+2009 |
THIN SPACE : 1em の 1/6 のスペース |
U+200A |
HAIR SPACE : THIN SPACE よりさらに狭いスペース |
U+202F |
NARROW NO-BREAK SPACE : THIN SPACE と同幅のノーブレークスペース |
U+205F |
MEDIUM MATHEMATICAL SPACE : 数学用スペース |
U+3000 |
IDEOGRAPHIC SPACE : 全角スペース |
とても多いですね。
これらすべてが対象となりトリムされるようです。
LineTerminator
WhiteSpace
のほうでだいぶ寄り道をしてしまいましたが、ECMA-262 での空白の定義には LineTerminator
も含まれます。
こちらは該当する文字が列挙されています。
Code Point | Name |
---|---|
U+000A |
LINE FEED (LF) <LF> |
U+000D |
CARRIAGE RETURN (CR) <CR> |
U+2028 |
LINE SEPARATOR <LS> |
U+2029 |
PARAGRAPH SEPARATOR <PS> |
いわゆる改行系の皆さんですね。
まとめ
というわけで、String.prototype.trim()
で消されるのは次の文字であることがわかりました。
Code Point | Name |
---|---|
U+0009 |
CHARACTER TABULATION |
U+000B |
LINE TABULATION |
U+000C |
FORM FEED (FF) |
U+FEFF |
ZERO WIDTH NO-BREAK SPACE |
U+0020 |
SPACE |
U+00A0 |
NO-BREAK SPACE |
U+1680 |
OGHAM SPACE MARK |
U+2000 |
EN QUAD |
U+2001 |
EM QUAD |
U+2002 |
EN SPACE |
U+2003 |
EM SPACE |
U+2004 |
THREE-PER-EM SPACE |
U+2005 |
FOUR-PER-EM SPACE |
U+2006 |
SIX-PER-EM SPACE |
U+2007 |
FIGURE SPACE |
U+2008 |
PUNCTUATION SPACE |
U+2009 |
THIN SPACE |
U+200A |
HAIR SPACE |
U+202F |
NARROW NO-BREAK SPACE |
U+205F |
MEDIUM MATHEMATICAL SPACE |
U+3000 |
IDEOGRAPHIC SPACE |
U+000A |
LINE FEED (LF) |
U+000D |
CARRIAGE RETURN (CR) |
U+2028 |
LINE SEPARATOR |
U+2029 |
PARAGRAPH SEPARATOR |
ここで出会って一生使うことのないかもしれない文字コードもあるかもしれませんが、勉強になりました。
余談 / なぜ調べる必要があったか?
通常では String.prototype.trim()
を使っていても、それが原因で特別問題になることは少ないかもしれません。
現在サイボウズ社内では kintone というサービスのフロントエンド刷新が進行中です。
その中で、10 年前に独自実装されたオリジナルの文字列トリム処理が存在しており、
「これってホントに String.prototype.trim()
に置き換えてもいいのか……??」と迷いが生まれたため、詳細に調べることになりました。
結果的には、既存のオリジナルの文字列トリム処理は、ほぼ String.prototype.trim()
と同等の処理であることがわかり、安心して置き換えることができました。
(10 年前の段階で、ちゃんと作ってあってすごいな〜という感想でした)
Discussion