文字列の書き方は3種類ある
JavaScriptでは文字列の区切り文字が3種類用意されています。そのうち2つはほぼ同じように使えますが、残りの1つは一段上のことができます。
シングルクォートとダブルクォートはまったく同じ文字列を生成するので、どちらかに決めて統一すればOKです(最近のコードベースではシングルクォートをデフォルトにするか、フォーマッタに任せるケースが多いですね)。一方、バッククォートで囲むと テンプレートリテラル になり、ここから面白い話になります。変数の埋め込みができたり、複数行にまたがって書けたりするんです。
内部的には、3種類とも同じプリミティブな文字列型を生成しています。区切り文字の違いは、あくまで書き方の選択肢にすぎません。
エスケープシーケンスとクォートの埋め込み
文字列の中でバックスラッシュを書くと、そこからエスケープシーケンスが始まります。よく使うのは \n(改行)、\t(タブ)、\\(バックスラッシュそのもの)、それから開始時と同じクォートを文字列内に入れたいときの \" や \' あたりです。
エスケープの大半は、もう片方のクォートを使うだけで避けられます。たとえば 'She said "hi" and left.' ならバックスラッシュは不要。原則に縛られず、実用重視で選びましょう。
テンプレートリテラル:バッククォートの本領
テンプレートリテラルには、普通の文字列ではできないことが2つあります。ひとつ目は、${...} による式の埋め込みです。
${...} の中には変数名だけでなく、JavaScript の式なら何でも書けます。四則演算、関数呼び出し、三項演算子、さらには別のテンプレートリテラルまで、基本的に何でもありです。
次に、テンプレートリテラルは\nを使わなくても複数行にまたがって書けます。
ソース中の改行は、文字列中の改行としてそのまま残ります。ちょっとしたラベル程度でない限り、バッククォートを使っておけばまず間違いありません。
文字列結合より埋め込み(テンプレートリテラル)
テンプレートリテラルが登場するまでは、動的な文字列を組み立てる手段は + 演算子しかありませんでした。
どちらも出力はまったく同じです。テンプレートリテラルは組み立てている文章そのままの形で読めますが、+ を使う書き方はクォート、スペース、結合演算子をいちいち頭の中で追う必要があります。+ は既存の文字列に短いものを1つ足すときだけに留めて、少しでも長くなるならバッククォートを使いましょう。
文字列はイミュータブル(不変)
文字列を書き換えているように見えるメソッドも、実際はすべて新しい文字列を返しています。
変更を反映したいなら、結果をどこかに代入する必要があります。初心者がハマりがちなのは s.replace("a", "b") と書いて s が書き換わると思い込むパターン。実際には変わりません。JavaScript の文字列は数値と同じで、値そのものは書き換え不可。できるのは変数が別の値を指すようにすることだけです。
実際によく使う JavaScript の文字列メソッド一覧
文字列の API は全部覚えようとするとかなりの量になります。普段の開発で手が伸びるのは、だいたい次の顔ぶれです。
覚えておくと得する小ネタをいくつか:
lengthはメソッドではなくプロパティ。カッコは付けません。replaceに普通の文字列を渡すと、最初にマッチした1箇所だけしか置換されません。全部置換したいときはreplaceAllを使うか、gフラグ付きの正規表現を渡しましょう。slice(start, end)の範囲は半開区間で、endの位置の文字は含まれません。sliceにマイナスの値を渡すと末尾から数えます。例えばs.slice(-3)なら末尾3文字が取れます。
文字列のインデックス参照と反復処理
JavaScript の文字列は、読み取り専用の文字配列のように振る舞います。インデックスでアクセスしたりループで回したりはできますが、特定の位置に代入することはできません:
for...of はコードポイント単位で反復するので、ほとんどの Unicode を正しく扱えます。一方で s[i] のようにインデックスでアクセスすると UTF-16 の「コードユニット」単位での反復になり、絵文字や BMP 外の文字が途中で分断されてしまうことがあります。普通の英数字テキストならどちらでも問題ありませんが、ユーザー入力を扱うなら for...of か [...s] を使うのが無難です。
Tagged Template Literals(タグ付きテンプレートリテラル)
あまり自分で書く機会はないかもしれませんが、もう一つ知っておきたい形があります。それが「タグ付き」テンプレートリテラルで、静的な文字列部分と埋め込み値を別々の引数として関数に渡せる仕組みです。
タグ関数が最終的な文字列の形を決めます。HTMLのエスケープ、SQLの安全な組み立て、CSSのコンパイル、GraphQLのパースなど、用途は自由自在です。このパターンは自分で書くよりも、ライブラリ(styled-components、graphql-tag、lit-html など)で目にすることの方が圧倒的に多いでしょう。ただ、独自の文字列処理が本当に必要になったときには、tagged template literals はいちばんきれいな解決策になります。
よくあるハマりどころ
つまずきやすいポイントを簡単にまとめておきます。
- メソッドの戻り値を代入し忘れる。
s.trim()は単体では何もしません。 - 数値と文字列を
+で連結してしまう。"Age: " + 30 + 5は"Age: 35"ではなく"Age: 305"になります。テンプレートリテラルを使えばこの問題は回避できます:`Age: ${30 + 5}`。 ==と文字列比較の混同。 等価性については後で詳しく扱いますが、"1" == 1はtrue、"1" === 1はfalseです。基本は===を使いましょう。lengthが文字数と同じだと思い込む。 絵文字などサロゲートペアを含む文字では、"".lengthは 1 ではなく 2 になります。ユーザーの目に映る文字数が欲しいときは[..."".length]やArray.from(s).lengthを使ってください。
次回: Number と BigInt
文字列はプリミティブ型のひとつにすぎません。もうひとつの代表が数値で、JavaScript の数値システムには独特のクセがあります。整数も浮動小数点数も同じ Number 型で扱い、それで足りない場面のために BigInt が用意されています。次のページで見ていきましょう。
よくある質問
クォートとバッククォートは何が違うの?
シングルクォート(')とダブルクォート(")はどちらも普通の文字列で、基本的に同じものと考えてOKです。一方、バッククォート(`)はテンプレートリテラルと呼ばれ、${...}で変数や式を埋め込めたり、エスケープなしで複数行にまたがる文字列が書けます。変数を差し込みたいとき、改行を含めたいときはバッククォート一択です。
文字列の中に変数を埋め込むには?
テンプレートリテラルを使います。文字列全体をバッククォートで囲み、変数や式を${...}の中に書くだけです。たとえば `Hello, ${name}!` と書けば、nameの値がその位置に展開されます。${a + b}や${user.name.toUpperCase()}のように式も書けますし、テンプレートリテラルを入れ子にすることも可能です。
JavaScriptの文字列はイミュータブル(不変)?
はい、イミュータブルです。toUpperCaseやslice、replaceといった文字列メソッドはすべて、元の文字列を書き換えるのではなく新しい文字列を返します。なのでs.toUpperCase()と書いても戻り値を変数に代入しなければ何も起こりません。この仕様はPythonやJavaの文字列と同じ考え方です。
タグ付きテンプレートリテラルって何?
テンプレートリテラルの前に関数を置くと、その関数が「文字列の配列」と「埋め込んだ値」を別々の引数として受け取ります。たとえば tag`Hello, ${name}` は tag(["Hello, ", ""], name) として呼び出されます。styled-componentsやgraphql-tagのようなライブラリは、この仕組みを使ってテンプレートをパースしたり加工したりしています。