概要
JavaScriptの文字列は、テキストデータを扱うための基本的なプリミティブ型である。
シングルクォート、ダブルクォート、テンプレートリテラル (バッククォート) の3種類の記法に対応しており、用途に応じて使い分けることができる。
文字列は不変 (イミュータブル) であり、一度生成した文字列は直接変更できない。
全ての文字列操作メソッドは元の文字列を変更せず、新しい文字列を生成して返す。
JavaScriptはUnicodeに対応しており、日本語を含む多言語の文字列を扱うことができる。
ただし、絵文字やサロゲートペア文字を含む場合は、length プロパティが文字数と一致しないことがある。
文字列操作のための豊富な組み込みメソッドが用意されており、検索、抽出、変換、置換、分割といった操作を簡潔に記述できる。
また、ES2015 (ES6) 以降ではテンプレートリテラルが導入され、文字列への式の埋め込みや複数行文字列の記述が容易になった。
さらに、タグ付きテンプレートリテラルを使うことで、文字列生成の処理をカスタマイズすることもできる。
文字列リテラル
文字列リテラルは、ソースコード上で文字列値を直接記述するための記法である。
JavaScriptでは3種類の記法が使用できる。
シングルクォートとダブルクォート
シングルクォート (') と ダブルクォート (") は機能的に同等であり、どちらで囲っても同じ文字列が生成される。
使い分けのポイントを以下に示す。
- 文字列内に同じ種類のクォートを含める場合
- 外側と異なるクォートを使用することでエスケープを回避できる。
- 例: '彼は"JavaScript"が好きと言った。' (ダブルクォートを内側に使用)
- 同じ種類のクォートを内側で使用する場合
- バックスラッシュ (
\) でエスケープが必要 - 例: 'It\'s a string'
- バックスラッシュ (
- プロジェクト内での統一
- コード可読性のために、プロジェクト内で統一した記法を使用することを推奨する。
下表に、主なエスケープシーケンスを示す。
| シーケンス | 意味 | 例 |
|---|---|---|
\n |
改行文字 | "Line1\nLine2" |
\t |
タブ文字 | "Column1\tColumn2" |
\\ |
バックスラッシュ | "C:\\Users\\name" |
\' |
シングルクォート | 'It\'s fine' |
\" |
ダブルクォート | "He said \"Hello\"" |
\uXXXX |
Unicode文字 (4桁16進数) | "\u2192" (→) |
\u{X...} |
Unicode文字 (ES6以降、1〜6桁) | "\u{1F600}" (絵文字) |
使用例を以下に示す。
const str1 = 'Hello';
const str2 = "World";
const newline = "Line1\nLine2";
const tab = "Column1\tColumn2";
const unicode = "\u2192 Arrow";
const escaped = 'It\'s a string';
テンプレートリテラル
テンプレートリテラルは、バッククォート (`) で囲った文字列記法である。
ES2015 (ES6) で導入された。
特徴を以下に示す。
${expression}の構文で任意のJavaScript式を埋め込める。- 改行がそのまま改行として扱われるため、複数行文字列を簡潔に記述できる。
- 文字列内にシングルクォートやダブルクォートを含めても、エスケープが不要
式の埋め込み
${} の中に変数や式を記述することにより、評価結果を文字列に埋め込むことができる。
const name = "Alice";
const age = 25;
// 変数の埋め込み
const greeting = `Hello, ${name}!`;
console.log(greeting); // "Hello, Alice!"
// 算術式の埋め込み
const calculation = `2 + 2 = ${2 + 2}`;
console.log(calculation); // "2 + 2 = 4"
// 三項演算子の埋め込み
const status = `Status: ${age >= 18 ? "Adult" : "Minor"}`;
console.log(status); // "Status: Adult"
// 関数呼び出しの埋め込み
const upper = `Uppercase: ${name.toUpperCase()}`;
console.log(upper); // "Uppercase: ALICE"
複数行文字列
テンプレートリテラルでは、ソースコード上の改行がそのまま文字列の改行として扱われる。
// シングル/ダブルクォートでの複数行 (エスケープが必要)
const oldStyle = "Line1\n" +
"Line2\n" +
"Line3";
// テンプレートリテラルでの複数行 (そのまま記述可能)
const multiLine = `Line1
Line2
Line3`;
// HTMLテンプレートの記述例
const html = `
<div class="card">
<h2>${"Title"}</h2>
<p>${"Content"}</p>
</div>
`;
文字列の基本操作
文字列の長さ
length プロパティは文字列の文字数を返す読み取り専用プロパティである。
const str = "Hello";
console.log(str.length); // 5
const empty = "";
console.log(empty.length); // 0
// 注意: 絵文字やサロゲートペアは2として数えられる場合がある
const emoji = "😀";
console.log(emoji.length); // 2 (サロゲートペアのため)
文字のアクセス
文字列の特定位置の文字にアクセスするための方法が3種類ある。
| メソッド | 説明 |
|---|---|
charAt(index)
|
|
ブラケット記法 ([index])
|
|
at(index) (ES2022)
|
|
const str = "Hello";
// charAt()
console.log(str.charAt(0)); // "H"
console.log(str.charAt(10)); // "" (空文字列)
// ブラケット記法
console.log(str[0]); // "H"
console.log(str[10]); // undefined
// at() (ES2022)
console.log(str.at(0)); // "H"
console.log(str.at(-1)); // "o" (最後の文字)
console.log(str.at(-2)); // "l" (最後から2番目)
文字列の不変性
JavaScriptの文字列はイミュータブル (不変) である。
1度生成した文字列は直接変更することができない。
const str = "Hello";
// 文字を直接変更しようとしても反映されない
str[0] = "J";
console.log(str); // "Hello" (変更されない)
// 新しい文字列を生成する必要がある
const newStr = "J" + str.slice(1);
console.log(newStr); // "Jello"
// 全ての文字列メソッドは新しい文字列を返す
const upper = str.toUpperCase();
console.log(str); // "Hello" (元の文字列は変わらない)
console.log(upper); // "HELLO" (新しい文字列)
検索メソッド
includes / startsWith / endsWith
これらのメソッドは、文字列の存在判定を行い、true または false を返す。
| メソッド | 説明 |
|---|---|
includes(searchString, position)
|
|
startsWith(searchString, position)
|
|
endsWith(searchString, endPosition)
|
|
const str = "Hello World";
// includes()
console.log(str.includes("World")); // true
console.log(str.includes("world")); // false (大文字小文字を区別する)
console.log(str.includes("World", 7)); // false (7番目以降で検索)
// startsWith()
console.log(str.startsWith("Hello")); // true
console.log(str.startsWith("World", 6)); // true (6番目から検索)
// endsWith()
console.log(str.endsWith("World")); // true
console.log(str.endsWith("Hello", 5)); // true (0〜5の範囲で検索)
indexOf / lastIndexOf
これらのメソッドは、部分文字列が出現する位置を返す。
見つからない場合は -1 を返す。
const str = "Hello World, Hello!";
// indexOf(): 最初に出現する位置を返す
console.log(str.indexOf("Hello")); // 0
console.log(str.indexOf("World")); // 6
console.log(str.indexOf("hello")); // -1 (大文字小文字を区別する)
console.log(str.indexOf("Hello", 1)); // 13 (1番目以降で検索)
// lastIndexOf(): 最後に出現する位置を返す
console.log(str.lastIndexOf("Hello")); // 13
console.log(str.lastIndexOf("o")); // 17
下表に、検索メソッドの比較を示す。
| メソッド | 戻り値 | 特徴 | 正規表現 |
|---|---|---|---|
includes() |
Boolean | 存在確認のみ | 不可 |
startsWith() |
Boolean | 先頭一致 | 不可 |
endsWith() |
Boolean | 末尾一致 | 不可 |
indexOf() |
Number | 最初の位置 / 見つからない場合は -1 | 不可 |
lastIndexOf() |
Number | 最後の位置 / 見つからない場合は -1 | 不可 |
search() |
Number | 最初の位置 / 見つからない場合は -1 | 可 |
抽出メソッド
slice
slice(start, end) は、指定した範囲の部分文字列を抽出して返す。
終了インデックスの文字は含まれない。
特徴を以下に示す。
- 負のインデックスをサポートしており、末尾からの位置を指定できる。
- 開始インデックスが終了インデックスより大きい場合は空文字列を返す。
- 元の文字列は変更されない。
const str = "Hello World";
console.log(str.slice(0, 5)); // "Hello"
console.log(str.slice(6)); // "World" (第2引数省略で末尾まで)
console.log(str.slice(-5)); // "World" (末尾から5文字)
console.log(str.slice(-5, -1)); // "Worl" (末尾から5文字〜最後から1文字前)
console.log(str.slice(5, 0)); // "" (開始 > 終了で空文字列)
substring
substring(start, end) は、slice() と似ているが、負のインデックスの扱いが異なる。
特徴を以下に示す。
- 負のインデックスは、0として扱われる。
- 開始インデックスが終了インデックスより大きい場合は、2つの引数が自動的に交換される。
- 元の文字列は変更されない。
const str = "Hello World";
console.log(str.substring(0, 5)); // "Hello"
console.log(str.substring(6)); // "World"
console.log(str.substring(-5)); // "Hello World" (負の値は0として扱われる)
console.log(str.substring(5, 0)); // "Hello" (引数が交換されて substring(0, 5) と同じ)
下表に、slice() と substring() の違いを示す。
| 項目 | slice() | substring() |
|---|---|---|
| 負のインデックス | 末尾からの位置として解釈 | 0として扱われる |
| 開始 > 終了 | 空文字列を返す | 引数が交換される |
| 推奨度 | 一般的に推奨 | 特定の用途に限定 |
一般的には、slice() の使用を推奨する。
負のインデックスをサポートしているため、末尾からのアクセスを直感的に記述できる。
変換メソッド
trim / trimStart / trimEnd
これらのメソッドは、文字列の先頭または末尾の空白文字を削除する。
削除される空白文字には、スペース、タブ、改行などが含まれる。
const str = " Hello World ";
// trim(): 両端の空白を削除
console.log(str.trim()); // "Hello World"
// trimStart(): 先頭の空白を削除
console.log(str.trimStart()); // "Hello World "
// trimEnd(): 末尾の空白を削除
console.log(str.trimEnd()); // " Hello World"
// フォームの入力値のトリム例
const userInput = " user@example.com ";
const email = userInput.trim();
console.log(email); // "user@example.com"
padStart / padEnd
これらのメソッドは、文字列が指定した長さになるように、先頭または末尾に文字を追加する。
数値のゼロ埋めや固定幅フォーマットに使用する。
// padStart(targetLength, padString): 先頭に文字を追加
const num = "5";
console.log(num.padStart(3, "0")); // "005" (ゼロ埋め)
console.log(num.padStart(3, " ")); // " 5" (スペース埋め)
// padEnd(targetLength, padString): 末尾に文字を追加
console.log(num.padEnd(3, "*")); // "5**"
// 実用例: 日付のフォーマット
const month = "3";
const day = "7";
const formatted = `${month.padStart(2, "0")}/${day.padStart(2, "0")}`;
console.log(formatted); // "03/07"
// 実用例: IDの固定幅フォーマット
const id = "42";
console.log(id.padStart(6, "0")); // "000042"
repeat
repeat(count) は、文字列を指定した回数だけ繰り返した新しい文字列を返す。
const str = "ab";
console.log(str.repeat(3)); // "ababab"
console.log(str.repeat(0)); // "" (空文字列)
console.log(str.repeat(2.5)); // "abab" (小数点は切り下げ)
// 実用例: インデントの生成
function indent(level) {
return " ".repeat(level * 2);
}
console.log(indent(3) + "text"); // " text"
置換メソッド
replace
replace(pattern, replacement) は、最初にマッチした部分文字列を置換する。
パターンには文字列または正規表現を使用できる。
const str = "hello hello";
// 文字列パターン: 最初の一致のみ置換
console.log(str.replace("hello", "hi")); // "hi hello"
// 正規表現 (グローバルフラグなし): 最初の一致のみ置換
console.log(str.replace(/hello/, "hi")); // "hi hello"
// 正規表現 (グローバルフラグ g): 全ての一致を置換
console.log(str.replace(/hello/g, "hi")); // "hi hi"
// 大文字小文字を区別しない置換 (iフラグ)
const str2 = "Hello hello";
console.log(str2.replace(/hello/gi, "hi")); // "hi hi"
// 置換関数の使用
const str3 = "hello world";
const result = str3.replace(/\b\w/g, (match) => match.toUpperCase());
console.log(result); // "Hello World"
replaceAll
replaceAll(pattern, replacement) は、全ての一致を置換する。
ES2021で導入されたメソッドであり、replace() にグローバルフラグを付けた場合と同等の結果を返す。
const str = "hello hello";
// 全ての一致を置換
console.log(str.replaceAll("hello", "hi")); // "hi hi"
// 正規表現を使用する場合は グローバルフラグ (g) が必須
console.log(str.replaceAll(/hello/g, "hi")); // "hi hi"
// グローバルフラグなしはエラーになる
// str.replaceAll(/hello/, "hi"); // TypeError
// 複数のパターンを置換する例
const str2 = "apple banana apple";
let result = str2.replaceAll("apple", "orange");
result = result.replaceAll("banana", "grape");
console.log(result); // "orange grape orange"
分割メソッド
split
split(separator, limit) は、文字列を指定した区切り文字で分割し、配列を返す。
区切り文字には文字列または正規表現を使用できる。
// 基本的な使用法
const str = "a,b,c,d";
console.log(str.split(",")); // ["a", "b", "c", "d"]
console.log(str.split("")); // ["a", ",", "b", ",", "c", ",", "d"]
console.log(str.split()); // ["a,b,c,d"] (引数なしは配列でラップ)
// limit パラメータ: 分割数の上限を指定
console.log(str.split(",", 2)); // ["a", "b"]
console.log(str.split(",", 1)); // ["a"]
// 正規表現による分割
const str2 = "Hello 123 World 456";
console.log(str2.split(/\d+/)); // ["Hello ", " World ", ""]
// キャプチャグループを含む分割
const str3 = "Hello 1 word. Sentence 2";
const splits = str3.split(/(\d)/);
console.log(splits); // ["Hello ", "1", " word. Sentence ", "2", ""]
// 実用例: CSVの解析
const csv = "Name,Age,City";
const headers = csv.split(",");
console.log(headers); // ["Name", "Age", "City"]
// 実用例: 文字列の反転
const reversed = "Hello".split("").reverse().join("");
console.log(reversed); // "olleH"
タグ付きテンプレートリテラル
基本概念
タグ付きテンプレートリテラルは、テンプレートリテラルの処理をカスタマイズする機能である。
関数名の直後にバッククォートで囲んだテンプレートリテラルを配置することで、関数がテンプレートの処理を制御できる。
// 通常のテンプレートリテラル
const name = "World";
const result1 = `Hello ${name}!`;
// タグ付きテンプレートリテラル (関数名をバッククォートの前に記述)
const result2 = myTag`Hello ${name}!`;
タグ関数の構造
タグ関数は以下の引数を受け取る。
- 第1引数 (strings)
- テンプレート内の文字列部分を格納した配列
- 式の間の文字列リテラルが順番に格納される。
- 残りの引数 (...values)
- テンプレート内の式の評価結果
- スプレッド構文で複数の値を受け取る。
タグ関数は必ずしも文字列を返す必要はなく、任意の値を返すことができる。
function myTag(strings, ...values) {
console.log(strings); // ["Hello ", "! You are ", " years old."]
console.log(values); // ["Alice", 25]
// strings と values を組み合わせて文字列を生成
return strings.reduce((result, str, i) => {
return result + str + (values[i] !== undefined ? values[i] : "");
}, "");
}
const name = "Alice";
const age = 25;
const result = myTag`Hello ${name}! You are ${age} years old.`;
console.log(result); // "Hello Alice! You are 25 years old."
ユースケース
タグ付きテンプレートリテラルは、以下に示すような用途に活用できる。
- HTMLエスケープ処理
function safeHtml(strings, ...values) { const escapeHtml = (str) => String(str) .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """); return strings.reduce((result, str, i) => { return result + str + (values[i] !== undefined ? escapeHtml(values[i]) : ""); }, ""); } const userInput = '<script>alert("XSS")</script>'; const html = safeHtml`<p>${userInput}</p>`; console.log(html); // <p><script>alert("XSS")</script></p>
- 国際化 (i18n) の実装
const translations = { ja: { welcome: "ようこそ", user: "ユーザ" }, en: { welcome: "Welcome", user: "User" } }; function i18n(lang) { return function(strings, ...values) { return strings.reduce((result, str, i) => { const value = values[i]; const translated = translations[lang][value] || value; return result + str + (value !== undefined ? translated : ""); }, ""); }; } const t = i18n("ja"); const name = "user"; console.log(t`${name}へのメッセージ`); // "ユーザへのメッセージ"
- CSS-in-JS (styled-components等のライブラリで使用される手法)
// styled-componentsのような使い方のイメージ function css(strings, ...values) { return strings.reduce((result, str, i) => { return result + str + (values[i] !== undefined ? values[i] : ""); }, ""); } const primaryColor = "#007bff"; const fontSize = "16px"; const styles = css` color: ${primaryColor}; font-size: ${fontSize}; font-weight: bold; `;
関連情報
- JavaScriptの基礎 - 変数宣言
- let / const / varの宣言方法、スコープ、ホイスティング
- JavaScriptの基礎 - プリミティブ型
- 7つのプリミティブ型、typeof演算子、型の自動変換
- JavaScriptの基礎 - 数値
- Number型の特性、算術演算子、Mathオブジェクト、BigInt
- JavaScriptの基礎 - 比較演算子と論理演算子
- ===/==の違い、短絡評価、Null合体演算子、オプショナルチェーン