`String` オブジェクト
この記事ではString オブジェクトについて説明します。
基本から実践テクニック、Unicode や正規表現に関連した落とし穴まで、ステップバイステップで分かりやすく解説します。
YouTube Video
String オブジェクト
JavaScript の文字列は、日常的な開発で最も頻繁に使う型の一つです。
プリミティブな文字列と String オブジェクトの違い
プリミティブな文字列("hello")と new String("hello") のようなラッパーオブジェクトは挙動が異なります。通常はプリミティブを使い、オブジェクトを使う必要はほとんどありません。
1// Primitive string
2const a = "hello";
3
4// String wrapper object
5const b = new String("hello");
6
7console.log(typeof a); // "string"
8console.log(typeof b); // "object"
9console.log(a === b); // false — wrapper objects are not strictly equal
- このコードはプリミティブとラッパーの型の違いと厳密比較での振る舞いを示します。通常は
new String()を避けてプリミティブを使いましょう。
文字列の生成方法(リテラルとテンプレートリテラル)
テンプレートリテラルは埋め込みや複数行に便利です。変数挿入と式評価が直感的にできます。
1const name = "Alice";
2const age = 30;
3
4// Template literal
5const greeting = `Name: ${name}, Age: ${age + 1}`;
6
7console.log(greeting); // "Name: Alice, Age: 31"
- テンプレートリテラルは可読性が高く、複数行の場合を含め、複雑な文字列組み立てに最適です。
代表的なメソッド(検索と部分抽出)
Stringオブジェクトには、様々な基本的なメソッドがあります。
1const text = "Hello, world! Hello again.";
2
3// search
4console.log(text.indexOf("Hello")); // 0
5console.log(text.indexOf("Hello", 1)); // 13
6console.log(text.includes("world")); // true
7console.log(text.startsWith("Hello")); // true
8console.log(text.endsWith("again.")); // true
9
10// slice / substring
11console.log(text.slice(7, 12)); // "world"
12console.log(text.substring(7, 12)); // "world"
sliceとsubstringは似ていますが、負のインデックスの扱いが異なります。sliceは負の値を末尾からの位置と解釈します。どちらを使うか明確にしておきましょう。
分割と結合(split / join)
文字列を配列に分割して処理し、再結合するパターンはよく利用されます。
1const csv = "red,green,blue";
2const arr = csv.split(","); // ["red","green","blue"]
3
4console.log(arr);
5console.log(arr.join(" | ")); // "red | green | blue"
splitで区切った配列をmapやfilterで加工し、joinで戻すという流れがよく使われます。
置換(replace)と正規表現
replace は最初にマッチした部分のみを置換します。全て置換したい場合は正規表現の g フラグを使います。置換関数を渡すことで動的な置換も可能です。
1const s = "foo 1 foo 2";
2
3// replace first only
4console.log(s.replace("foo", "bar")); // "bar 1 foo 2"
5
6// replace all using regex
7console.log(s.replace(/foo/g, "bar")); // "bar 1 bar 2"
8
9// replace with function
10const r = s.replace(/\d+/g, (match) => String(Number(match) * 10));
11console.log(r); // "foo 10 foo 20"
- 関数を使う動的な置換で、マッチを解析して変形する処理が簡潔に書けます。
大文字小文字変換と正規化
多言語対応や比較では toLowerCase や toUpperCase に加えて、Unicode 正規化(normalize)が重要です。特にアクセント付き文字の比較で必要になります。
1// Case conversion example:
2// "\u00DF" represents the German letter "ß".
3// In some locales, converting "ß" to uppercase becomes "SS".
4// JavaScript follows this behavior.
5console.log("\u00DF");
6console.log("\u00DF".toUpperCase()); // "SS"
7
8// Unicode normalization example:
9// "e\u0301" is "e" + a combining acute accent.
10// "\u00e9" is the precomposed character "é".
11// These two look the same but are different code point sequences.
12const a = "e\u0301";
13const b = "\u00e9";
14
15console.log(a === b); // false: different underlying code points
16console.log(a.normalize() === b.normalize()); // true: normalized to the same form
- 合字・結合文字といった Unicode の表現違いはそのままだと等しくならないため
normalize()を使ってから比較します。
Unicode とコードポイント(サロゲートペアの扱い)
JavaScript の文字列は UTF-16 コードユニット列なので、絵文字などは1文字が2つのコードユニットになることがあります。正しい「文字」単位で処理するには Array.from やスプレッド、for...of を使います。
1// Emoji composed with multiple code points:
2// "\u{1F469}" = woman, "\u{200D}" = Zero Width Joiner (ZWJ),
3// "\u{1F4BB}" = laptop. Combined, they form a single emoji: 👩💻
4const s = "\u{1F469}\u{200D}\u{1F4BB}";
5console.log(s);
6
7// Length in UTF-16 code units (not actual Unicode characters):
8// Because this emoji uses surrogate pairs + ZWJ, the length may be > 1.
9console.log("Length:", s.length);
10
11// Iterate by Unicode code points (ES6 for...of iterates code points):
12// Each iteration gives a full Unicode character, not UTF-16 units.
13for (const ch of s) {
14 console.log(ch);
15}
16
17// Convert to an array of Unicode characters:
18console.log(Array.from(s));lengthはコードユニット長を返すため絵文字や合字で期待と違う値になります。for...ofやArray.fromは「表示文字(grapheme cluster)」に近い単位で扱えますが、完全なグラフェム処理が必要なら専用ライブラリを検討してください。
安全な正規表現置換(ユーザー入力を扱うとき)
ユーザー入力を正規表現に埋め込む場合、エスケープを忘れると予期しない振る舞いや脆弱性につながります。パターンを作る前にエスケープしましょう。
1function escapeRegex(s) {
2 return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3}
4
5const userInput = "a+b";
6const pattern = new RegExp(escapeRegex(userInput), "g");
7console.log("a+b a+b".replace(pattern, "X")); // "X X"
- ユーザー文字列はそのまま正規表現に使うのではなく、必ずエスケープしてから組み立てます。
パフォーマンスのヒント:連結とテンプレート
多くの小さな文字列を順に連結する場合、配列に入れて join する方が効率的な場合があります。一方テンプレート文字列は可読性が高く、ほとんどのケースで十分高速です。
1// concatenation in loop (less ideal)
2let s = "";
3for (let i = 0; i < 1000; i++) {
4 s += i + ",";
5}
6
7// using array + join (often faster for many pieces)
8const parts = [];
9for (let i = 0; i < 1000; i++) {
10 parts.push(i + ",");
11}
12const s2 = parts.join("");- モダンな JavaScript エンジンでは多くの最適化が行われるため、少ない回数の文字列連結であれば性能を気にする必要はありません。ただし、数万回規模の連結を行う場合は、
joinを使った方法のほうが効率的になることがあります。
便利な実用テクニック:パディング、トリム、繰り返し
trim、padStart、padEnd、repeat は、日常的な文字列処理で特に役立つ便利なメソッドです。入力値の整形や出力フォーマットの統一など、実用的な場面でもよく使用されます。
1console.log(" hello ".trim()); // "hello"
2console.log("5".padStart(3, "0")); // "005"
3console.log("x".repeat(5)); // "xxxxx"
- フォーム入力の正規化や固定幅出力などにこれらのメソッドを活用できます。
文字列比較(ロケール比較)
言語ごとの辞書順比較には localeCompare が有効です。オプションで言語や感度(大文字・小文字の扱いなど)を指定できます。
1console.log(
2 "\u00E4".localeCompare("z", "de")
3); // may be -1 or other depending on locale
4
5console.log(
6 "a".localeCompare("A", undefined, { sensitivity: "base" })
7); // 0
- 国際化対応が必要な比較では
localeCompareを使い、適切なロケールとオプションを指定してください。
実践例:CSV 行からオブジェクトへ変換(実用ワークフロー)
よくあるユースケースとして CSV の1行をパースしてオブジェクトにする例で、split と trim、map を組み合わせます。クォート付きフィールドや複雑な CSV には専用パーサーを使ってください。
1// simple CSV parse (no quotes handling)
2function parseCsvLine(line, headers) {
3 const values = line.split(",").map(v => v.trim());
4 const obj = {};
5 headers.forEach((h, i) => obj[h] = values[i] ?? null);
6 return obj;
7}
8
9const headers = ["name", "age", "city"];
10const line = " Alice , 30 , New York ";
11console.log(parseCsvLine(line, headers));
12// { name: "Alice", age: "30", city: "New York" }
- シンプルな CSV であればこの方法で処理できますが、引用符付きフィールド内にカンマが含まれるようなケースには対応できない点に注意してください。
よくある落とし穴
JavaScript の文字列操作では、見落としやすい仕様や誤解されやすい挙動がいくつかあります。思わぬバグを避けるためにも、以下のポイントを押さえておくことが大切です。
new String()を使用すると、型判定や比較で誤った結果を招く可能性があります。多くの場合はプリミティブな文字列型だけで十分です。- Unicode では、見た目の1文字が複数のコードユニットで構成される場合があります。そのため、
lengthと実際の表示上の文字数が一致しないことがあります。 - ユーザー入力を正規表現に組み込む際には、必ずエスケープ処理を行う必要があります。
String.prototype.replace()は、既定では最初の一致のみを置換します。すべて置換したい場合は、正規表現の/gフラグを利用します。- 文字列は不変(immutable)であるため、操作結果は常に新しい文字列として返されます。返り値を必ず受け取る設計にすることが重要です。
まとめ
JavaScript の文字列は一見シンプルに見えても、Unicode や不変性に関する特性を正しく理解することが大切です。基本を押さえることで、文字列処理の信頼性と可読性が大きく向上します。
YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。