2024年1月17日

文字列

JavaScriptでは、テキストデータは文字列として格納されます。単一の文字のための個別の型はありません。

文字列の内部形式は常にUTF-16であり、ページエンコーディングには依存しません。

引用符

引用符の種類を思い出してみましょう。

文字列は、シングルクォート、ダブルクォート、またはバッククォートで囲むことができます。

let single = 'single-quoted';
let double = "double-quoted";

let backticks = `backticks`;

シングルクォートとダブルクォートは基本的に同じです。しかし、バッククォートを使用すると、${…}で囲むことで、任意の式を文字列に埋め込むことができます。

function sum(a, b) {
  return a + b;
}

alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3.

バッククォートを使用するもう一つの利点は、文字列を複数行にまたがって記述できることです。

let guestList = `Guests:
 * John
 * Pete
 * Mary
`;

alert(guestList); // a list of guests, multiple lines

自然に見えますよね?しかし、シングルクォートやダブルクォートではこのように動作しません。

これらを使用し、複数行にまたがって記述しようとするとエラーが発生します。

let guestList = "Guests: // Error: Unexpected token ILLEGAL
  * John";

シングルクォートとダブルクォートは、複数行の文字列の必要性が考慮されていなかった、言語作成の古い時代に登場しました。バッククォートは後になって登場したため、より多用途です。

バッククォートでは、最初のバッククォートの前に「テンプレート関数」を指定することもできます。構文はfunc`string`です。関数funcは自動的に呼び出され、文字列と埋め込まれた式を受け取り、それらを処理できます。この機能は「タグ付きテンプレート」と呼ばれ、めったに見られませんが、MDNで詳しく読むことができます: テンプレートリテラル

特殊文字

いわゆる「改行文字」である\nを使用することで、シングルクォートやダブルクォートでも複数行の文字列を作成することができます。これは改行を表します。

let guestList = "Guests:\n * John\n * Pete\n * Mary";

alert(guestList); // a multiline list of guests, same as above

より簡単な例として、以下の2行は、記述方法が異なるだけで、同じです。

let str1 = "Hello\nWorld"; // two lines using a "newline symbol"

// two lines using a normal newline and backticks
let str2 = `Hello
World`;

alert(str1 == str2); // true

他にも、あまり一般的ではない特殊文字があります。

文字 説明
\n 改行
\r Windowsのテキストファイルでは、2つの文字\r\nの組み合わせが新しい改行を表しますが、Windows以外のOSでは\nのみです。これは歴史的な理由によるもので、ほとんどのWindowsソフトウェアも\nを理解します。
\'\"\` 引用符
\\ バックスラッシュ
\t タブ
\b, \f, \v バックスペース、フォームフィード、垂直タブ - 完全性のために言及しましたが、古い時代のもので、最近では使用されません(今すぐ忘れても構いません)。

ご覧のとおり、すべての特殊文字はバックスラッシュ文字\で始まります。これは「エスケープ文字」とも呼ばれます。

これは非常に特殊であるため、文字列内に実際のバックスラッシュ\を表示する必要がある場合は、それを2倍にする必要があります。

alert( `The backslash: \\` ); // The backslash: \

いわゆる「エスケープされた」引用符\'\"\`は、同じ引用符で囲まれた文字列内に引用符を挿入するために使用されます。

例えば

alert( 'I\'m the Walrus!' ); // I'm the Walrus!

ご覧のとおり、内側の引用符はバックスラッシュ\'で前置する必要があります。そうしないと、文字列の終わりを示すことになってしまうからです。

もちろん、エスケープする必要があるのは、囲んでいる引用符と同じ引用符だけです。したがって、よりエレガントな解決策として、代わりにダブルクォートまたはバッククォートに切り替えることができます。

alert( "I'm the Walrus!" ); // I'm the Walrus!

これらの特殊文字に加えて、Unicodeコード\u…の特殊な表記法もあります。これはめったに使用されず、Unicodeに関するオプションの章で説明します。

文字列の長さ

lengthプロパティは文字列の長さを持っています。

alert( `My\n`.length ); // 3

\nは単一の「特殊」文字であるため、長さは確かに3であることに注意してください。

lengthはプロパティです

他の言語のバックグラウンドを持つ人は、str.lengthの代わりにstr.length()とタイプミスすることがあります。これは機能しません。

str.lengthは数値プロパティであり、関数ではないことに注意してください。後に括弧を追加する必要はありません。.length()ではなく、.lengthです。

文字へのアクセス

位置posの文字を取得するには、角かっこ[pos]を使用するか、str.at(pos)メソッドを呼び出します。最初の文字はゼロの位置から始まります。

let str = `Hello`;

// the first character
alert( str[0] ); // H
alert( str.at(0) ); // H

// the last character
alert( str[str.length - 1] ); // o
alert( str.at(-1) );

ご覧のとおり、.at(pos)メソッドには負のポジションを許可するという利点があります。posが負の場合、文字列の末尾から数えられます。

したがって、.at(-1)は最後の文字を意味し、.at(-2)はその前の文字などとなります。

角かっこは、負のインデックスに対して常にundefinedを返します。例えば

let str = `Hello`;

alert( str[-2] ); // undefined
alert( str.at(-2) ); // l

for..ofを使用して文字を反復処理することもできます。

for (let char of "Hello") {
  alert(char); // H,e,l,l,o (char becomes "H", then "e", then "l" etc)
}

文字列は不変です

JavaScriptでは、文字列は変更できません。文字を変更することは不可能です。

それが機能しないことを示すために試してみましょう。

let str = 'Hi';

str[0] = 'h'; // error
alert( str[0] ); // doesn't work

通常の回避策は、古い文字列の代わりに新しい文字列を作成し、それをstrに割り当てることです。

例えば

let str = 'Hi';

str = 'h' + str[1]; // replace the string

alert( str ); // hi

次のセクションでは、この例をさらに見ていきます。

大文字と小文字の変更

toLowerCase()メソッドとtoUpperCase()メソッドは大文字と小文字を変更します。

alert( 'Interface'.toUpperCase() ); // INTERFACE
alert( 'Interface'.toLowerCase() ); // interface

または、単一の文字を小文字にしたい場合

alert( 'Interface'[0].toLowerCase() ); // 'i'

部分文字列の検索

文字列内で部分文字列を検索する方法は複数あります。

str.indexOf

最初のメソッドは、str.indexOf(substr, pos)です。

与えられた位置posから開始して、str内でsubstrを探し、一致が見つかった位置を返します。何も見つからない場合は-1を返します。

例えば

let str = 'Widget with id';

alert( str.indexOf('Widget') ); // 0, because 'Widget' is found at the beginning
alert( str.indexOf('widget') ); // -1, not found, the search is case-sensitive

alert( str.indexOf("id") ); // 1, "id" is found at the position 1 (..idget with id)

オプションの2番目のパラメータを使用すると、指定された位置から検索を開始できます。

例えば、"id"の最初の一致は位置1にあります。次の一致を探すには、位置2から検索を開始しましょう。

let str = 'Widget with id';

alert( str.indexOf('id', 2) ) // 12

すべての一致に関心がある場合は、ループでindexOfを実行できます。新しい呼び出しは、前のマッチ後の位置で行われます。

let str = 'As sly as a fox, as strong as an ox';

let target = 'as'; // let's look for it

let pos = 0;
while (true) {
  let foundPos = str.indexOf(target, pos);
  if (foundPos == -1) break;

  alert( `Found at ${foundPos}` );
  pos = foundPos + 1; // continue the search from the next position
}

同じアルゴリズムをより短くレイアウトできます。

let str = "As sly as a fox, as strong as an ox";
let target = "as";

let pos = -1;
while ((pos = str.indexOf(target, pos + 1)) != -1) {
  alert( pos );
}
str.lastIndexOf(substr, position)

文字列の末尾から先頭に向かって検索する同様のメソッドstr.lastIndexOf(substr, position)もあります。

逆順で一致をリストします。

ifテストでindexOfを使用すると、わずかな不便さがあります。このようにifに入れることはできません。

let str = "Widget with id";

if (str.indexOf("Widget")) {
    alert("We found it"); // doesn't work!
}

上記の例のalertは表示されません。なぜなら、str.indexOf("Widget")0を返す(開始位置で一致が見つかったことを意味する)からです。その通りですが、if0falseと見なします。

したがって、実際にはこのように-1をチェックする必要があります。

let str = "Widget with id";

if (str.indexOf("Widget") != -1) {
    alert("We found it"); // works now!
}

includes、startsWith、endsWith

よりモダンなメソッドstr.includes(substr, pos)は、strsubstrが含まれているかどうかに応じて、true/falseを返します。

一致をテストする必要があるが、その位置は必要ない場合に適切な選択です。

alert( "Widget with id".includes("Widget") ); // true

alert( "Hello".includes("Bye") ); // false

str.includesのオプションの2番目の引数は、検索を開始する位置です。

alert( "Widget".includes("id") ); // true
alert( "Widget".includes("id", 3) ); // false, from position 3 there is no "id"

str.startsWithメソッドとstr.endsWithメソッドは、その名前が示すとおりに動作します。

alert( "Widget".startsWith("Wid") ); // true, "Widget" starts with "Wid"
alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get"

部分文字列の取得

JavaScriptで部分文字列を取得する方法は3つあります: substringsubstrslice

str.slice(start [, end])

文字列のstartからend(ただし、endは含まない)までの部分を返します。

例えば

let str = "stringify";
alert( str.slice(0, 5) ); // 'strin', the substring from 0 to 5 (not including 5)
alert( str.slice(0, 1) ); // 's', from 0 to 1, but not including 1, so only character at 0

2番目の引数がない場合、sliceは文字列の末尾まで続きます。

let str = "stringify";
alert( str.slice(2) ); // 'ringify', from the 2nd position till the end

start/endに負の値も可能です。これは、位置が文字列の末尾から数えられることを意味します。

let str = "stringify";

// start at the 4th position from the right, end at the 1st from the right
alert( str.slice(-4, -1) ); // 'gif'
str.substring(start [, end])

startendendは含まない)の文字列の部分を返します。

これはsliceとほぼ同じですが、startendよりも大きくなることを許可します(この場合、単にstartendの値を入れ替えます)。

例えば

let str = "stringify";

// these are same for substring
alert( str.substring(2, 6) ); // "ring"
alert( str.substring(6, 2) ); // "ring"

// ...but not for slice:
alert( str.slice(2, 6) ); // "ring" (the same)
alert( str.slice(6, 2) ); // "" (an empty string)

(sliceとは異なり)負の引数はサポートされておらず、0として扱われます。

str.substr(start [, length])

与えられたlengthで、startからの文字列の部分を返します。

前のメソッドとは対照的に、このメソッドでは終了位置の代わりにlengthを指定できます。

let str = "stringify";
alert( str.substr(2, 4) ); // 'ring', from the 2nd position get 4 characters

最初の引数は、末尾から数えるために負の値を指定できます。

let str = "stringify";
alert( str.substr(-4, 2) ); // 'gi', from the 4th position get 2 characters

このメソッドは、言語仕様のAnnex Bに記載されています。つまり、ブラウザでホストされたJavaScriptエンジンのみがサポートすべきであり、使用は推奨されません。実際には、どこでもサポートされています。

混乱を避けるため、これらのメソッドをまとめておきましょう。

メソッド 選択… 負数
slice(start, end) startからendまで(endは含まない) 負数を許可
substring(start, end) startendの間(endは含まない) 負数は0を意味する
substr(start, length) startからlength文字を取得 負のstartを許可
どれを選ぶべきか?

どれでも同じことができます。形式的には、substrにはわずかな欠点があります。JavaScriptのコア仕様ではなく、主に歴史的な理由で存在するブラウザ限定の機能をカバーするAnnex Bに記述されていることです。そのため、ブラウザ以外の環境ではサポートされない可能性があります。しかし、実際にはどこでも動作します。

他の2つの選択肢のうち、sliceは少し柔軟性があり、負の引数を許可し、記述も短くて済みます。

したがって、実用上はsliceだけを覚えておけば十分です。

文字列の比較

比較の章で説明したように、文字列はアルファベット順に文字ごとに比較されます。

ただし、いくつかの奇妙な点があります。

  1. 小文字は常に大文字よりも大きい

    alert( 'a' > 'Z' ); // true
  2. 発音区別記号付きの文字は「順不同」である

    alert( 'Österreich' > 'Zealand' ); // true

    これにより、これらの国名をソートすると奇妙な結果になる可能性があります。通常、人々はリストでZealandÖsterreichの後に来ることを期待するでしょう。

何が起こっているかを理解するために、JavaScriptの文字列がUTF-16を使用してエンコードされていることを知っておく必要があります。つまり、各文字には対応する数値コードがあります。

コードから文字を取得したり、その逆を行ったりするための特別なメソッドがあります。

str.codePointAt(pos)

位置posの文字のコードを表す10進数を返します

// different case letters have different codes
alert( "Z".codePointAt(0) ); // 90
alert( "z".codePointAt(0) ); // 122
alert( "z".codePointAt(0).toString(16) ); // 7a (if we need a hexadecimal value)
String.fromCodePoint(code)

数値codeで文字を作成します

alert( String.fromCodePoint(90) ); // Z
alert( String.fromCodePoint(0x5a) ); // Z (we can also use a hex value as an argument)

それでは、コード65..220(ラテンアルファベットと少し追加)を持つ文字を、それらの文字列を作成して見てみましょう。

let str = '';

for (let i = 65; i <= 220; i++) {
  str += String.fromCodePoint(i);
}
alert( str );
// Output:
// ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„
// ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜ

見てください。大文字が最初に来て、いくつかの特殊文字、次に小文字、そして出力の最後近くにÖがあります。

これで、なぜa > Zなのかが明らかになりました。

文字は数値コードによって比較されます。コードが大きいほど、文字が大きくなります。a(97)のコードは、Z(90)のコードよりも大きいです。

  • すべての小文字は、コードが大きいため、大文字の後に来ます。
  • Öのような一部の文字は、メインのアルファベットから離れて立っています。ここでは、コードがaからzまでのどれよりも大きいです。

正しい比較

文字列比較を行う「正しい」アルゴリズムは、言語によってアルファベットが異なるため、思ったよりも複雑です。

そのため、ブラウザは比較する言語を知る必要があります。

幸いなことに、最新のブラウザは国際化標準ECMA-402をサポートしています。

これは、言語の規則に従って、異なる言語で文字列を比較するための特別なメソッドを提供します。

str.localeCompare(str2)を呼び出すと、言語規則に従ってstrstr2よりも小さいか、等しいか、大きいかを示す整数が返されます。

  • strstr2より小さい場合は負の数を返します。
  • strstr2より大きい場合は正の数を返します。
  • 等しい場合は0を返します。

例えば

alert( 'Österreich'.localeCompare('Zealand') ); // -1

このメソッドには、ドキュメントで指定されている2つの追加引数があり、言語(デフォルトでは環境から取得、文字順は言語に依存)を指定したり、大文字と小文字の区別や、"a""á"を同じものとして扱うべきかなどの追加ルールを設定したりできます。

まとめ

  • 引用符には3つのタイプがあります。バッククォートを使用すると、文字列を複数行にまたがらせたり、式${…}を埋め込んだりできます。
  • 改行\nなどの特殊文字を使用できます。
  • 文字を取得するには、[]またはatメソッドを使用します。
  • 部分文字列を取得するには、sliceまたはsubstringを使用します。
  • 文字列を小文字/大文字にするには、toLowerCase/toUpperCaseを使用します。
  • 部分文字列を検索するには、indexOf、または単純なチェックの場合はincludes/startsWith/endsWithを使用します。
  • 言語に従って文字列を比較するには、localeCompareを使用します。それ以外の場合は、文字コードによって比較されます。

文字列には、他にも役立つメソッドがいくつかあります。

  • str.trim() – 文字列の先頭と末尾のスペースを削除(「トリミング」)します。
  • str.repeat(n) – 文字列をn回繰り返します。
  • …およびマニュアルに記載されているその他。

文字列には、正規表現を使用した検索/置換を行うためのメソッドもあります。しかし、それは大きなトピックなので、別のチュートリアルセクション正規表現で説明します。

また、現在では、文字列がUnicodeエンコーディングに基づいているため、比較に問題があることを知っておくことが重要です。Unicodeの詳細については、Unicode、文字列の内部の章で説明します。

課題

重要度: 5

文字列strの最初の文字を大文字にした文字列を返す関数ucFirst(str)を記述してください。例:

ucFirst("john") == "John";

テスト付きのサンドボックスを開きます。

JavaScriptの文字列は不変であるため、最初の文字を「置き換える」ことはできません。

ただし、既存の文字列に基づいて、最初の文字を大文字にした新しい文字列を作成できます。

let newStr = str[0].toUpperCase() + str.slice(1);

ただし、小さな問題があります。strが空の場合、str[0]undefinedになり、undefinedにはtoUpperCase()メソッドがないため、エラーが発生します。

最も簡単な解決策は、次のように空の文字列のテストを追加することです。

function ucFirst(str) {
  if (!str) return str;

  return str[0].toUpperCase() + str.slice(1);
}

alert( ucFirst("john") ); // John

サンドボックスでテスト付きのソリューションを開きます。

重要度: 5

strに'viagra'または'XXX'が含まれている場合はtrueを、それ以外の場合はfalseを返す関数checkSpam(str)を記述します。

関数は大文字と小文字を区別しない必要があります

checkSpam('buy ViAgRA now') == true
checkSpam('free xxxxx') == true
checkSpam("innocent rabbit") == false

テスト付きのサンドボックスを開きます。

検索で大文字と小文字を区別しないようにするために、文字列を小文字にしてから検索してみましょう。

function checkSpam(str) {
  let lowerStr = str.toLowerCase();

  return lowerStr.includes('viagra') || lowerStr.includes('xxx');
}

alert( checkSpam('buy ViAgRA now') );
alert( checkSpam('free xxxxx') );
alert( checkSpam("innocent rabbit") );

サンドボックスでテスト付きのソリューションを開きます。

重要度: 5

strの長さをチェックし、maxlengthを超える場合は、strの末尾を省略記号"…"に置き換えて、長さをmaxlengthにする関数truncate(str, maxlength)を作成します。

関数の結果は、(必要に応じて)切り詰められた文字列である必要があります。

例えば

truncate("What I'd like to tell on this topic is:", 20) == "What I'd like to te…"

truncate("Hi everyone!", 20) == "Hi everyone!"

テスト付きのサンドボックスを開きます。

最大長はmaxlengthである必要があるため、省略記号のスペースを確保するために少し短くする必要があります。

省略記号の単一のUnicode文字があることに注意してください。それは3つのドットではありません。

function truncate(str, maxlength) {
  return (str.length > maxlength) ?
    str.slice(0, maxlength - 1) + '…' : str;
}

サンドボックスでテスト付きのソリューションを開きます。

重要度: 4

"$120"の形式でコストがあります。つまり、ドル記号が最初にあり、その後に数値が続きます。

このような文字列から数値を抽出し、それを返す関数extractCurrencyValue(str)を作成します。

alert( extractCurrencyValue('$120') === 120 ); // true

テスト付きのサンドボックスを開きます。

function extractCurrencyValue(str) {
  return +str.slice(1);
}

サンドボックスでテスト付きのソリューションを開きます。

チュートリアルマップ

コメント

コメントする前にこれを読んでください…
  • 改善点について提案がある場合は、コメントする代わりにGitHub issueを送信するか、プルリクエストを送信してください。
  • 記事の内容を理解できない場合は、詳しく説明してください。
  • コードを数語挿入するには、<code>タグを使用し、数行の場合は<pre>タグでラップし、10行を超える場合はサンドボックス(plnkrjsbincodepen…)を使用してください。