【JavaScript】特定の文字列の直後にある1文字を抽出する方法|indexOf・slice・正規表現(lookbehind)と末尾・絵文字対策まで解説

JavaScriptで「特定の文字列のすぐ後ろにある1文字を取り出したい」という場面はよくあります。たとえば key=value= の直後(値の先頭文字)を調べたり、ファイル名の . の直後(拡張子の先頭)を取得したりするケースです。

基本はindexOf() で検索文字列の位置を求め、その文字数ぶん後ろの位置を見る」という流れになります。この記事では indexOf()slice()・正規表現(lookbehind)の3通りを比較しながら、「検索文字列が末尾にあって後ろに文字が無い」「見つからない」「絵文字で文字化けする」といった落とし穴の対策まで解説します。

この記事の結論:まず indexOf() で位置を取得し、-1(見つからない)と位置 + 検索文字列の長さが文字列の末尾を超えていないかをチェックしてから str[i + search.length] で直後の文字を取り出すのが安全です。
逆に「直前の1文字」を取得したい場合は、姉妹記事特定の文字列の直前にある1文字を抽出する方法で詳しく解説しています。
スポンサーリンク

indexOf() で取得する(基本・推奨)

indexOf() で検索文字列の位置を取得し、そこに検索文字列の長さを足した位置が「直後の文字」のインデックスです。

indexOf() で直後の1文字を取得
const str = "abcdefg";
const search = "c";

const index = str.indexOf(search);   // "c" の位置(=2)
if (index !== -1) {
  const after = index + search.length; // 直後の位置(=3)
  console.log(str[after]);             // "d"
}

安全なヘルパー関数として実装する

実務では「見つからない」「検索文字列が末尾にあって後ろに文字が無い」の2つを毎回チェックするのは冗長なので、関数化しておくと安全です。

getCharAfter(堅牢版)
function getCharAfter(str, search) {
  const index = str.indexOf(search);
  if (index === -1) return null;          // 見つからない
  const after = index + search.length;
  return after < str.length ? str[after] : null; // 末尾なら後ろに文字なし
}

console.log(getCharAfter("Hello World", "Hello")); // " "(スペース)
console.log(getCharAfter("user=tanaka", "="));     // "t"
console.log(getCharAfter("abc", "abc"));           // null(後ろに文字がない)
console.log(getCharAfter("abc", "z"));             // null(見つからない)
2つの境界に注意:「見つからない(indexOf() === -1)」と「検索文字列が末尾にあって後ろに文字が無い(after === str.length)」は別物です。どちらも処理しないと、後者で undefined が返ってバグの原因になります。

slice() / charAt() で取得する

ブラケット記法 str[after] の代わりに、slice()charAt() でも同じ位置の文字を取得できます。範囲外のときの戻り値だけが異なります。

slice() / charAt() で取得
const str = "abcdefg";
const after = str.indexOf("c") + 1; // 3

console.log(str.slice(after, after + 1)); // "d"
console.log(str.charAt(after));           // "d"

// 範囲外(末尾を超える位置)の戻り値の違い
console.log(str[99]);          // undefined
console.log(str.charAt(99));   // ""(空文字)
console.log(str.slice(99, 100)); // ""(空文字)

直後のN文字を取得する

1文字だけでなく「直後のN文字」が欲しいときは slice() が便利です。開始位置と文字数の詳しい指定方法は開始位置と文字数を指定して取得する方法を参照してください。

直後のN文字
function getCharsAfter(str, search, n = 1) {
  const index = str.indexOf(search);
  if (index === -1) return null;
  const after = index + search.length;
  return str.slice(after, after + n); // 末尾を超えても安全(自動で切り詰め)
}

console.log(getCharsAfter("price:1000yen", ":", 4)); // "1000"
console.log(getCharsAfter("abc", "ab", 5));          // "c"(残り全部)

正規表現で取得する

パターンで柔軟に取り出したいときは正規表現が便利です。match() でキャプチャグループや lookbehind を使います。

キャプチャグループで取得する

検索文字列の直後の1文字を (.) でキャプチャします。検索文字列を変数で渡すときは、.( などの特殊文字を必ずエスケープします。

キャプチャグループ + エスケープ
function escapeRegExp(s) {
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

const str = "abcdefg";
const search = "c";

const regex = new RegExp(escapeRegExp(search) + "(.)");
const match = str.match(regex);
console.log(match ? match[1] : null); // "d"(キャプチャグループ1)

肯定後読み(lookbehind)で取得する

肯定後読み (?<=...) を使うと、検索文字列自体を結果に含めずに直後の文字だけを取得できます。

lookbehind で取得
const str = "price:100";

// ":" の直後の1文字を後読みで取得
const match = str.match(/(?<=:)./);
console.log(match ? match[0] : null); // "1"
注意:後読み(lookbehind)は ES2018 で追加された機能で、IE11では使えません(Chrome 62+・Firefox 78+・Safari 16.4+ で利用可)。また、いずれの正規表現も検索文字列が末尾にあって後ろに文字が無い場合はマッチしません(「見つからない」と区別したいときは indexOf() 方式が確実です)。

最後の出現の直後を取得する(lastIndexOf)

検索文字列が複数回登場する文字列で「最後の出現の直後」を取りたい場合は、indexOf() の代わりに lastIndexOf() を使います。

lastIndexOf() で最後の出現の直後を取得
function getCharAfterLast(str, search) {
  const index = str.lastIndexOf(search);
  if (index === -1) return null;
  const after = index + search.length;
  return after < str.length ? str[after] : null;
}

// 最後の "." の直後 → 拡張子の先頭文字
console.log(getCharAfterLast("archive.tar.gz", ".")); // "g"
console.log(getCharAfterLast("photo.JPG", "."));      // "J"

全出現箇所の直後文字を一括取得する

検索文字列が複数回出現する場合、すべての直後文字をまとめて取得できます。

indexOf() のループで全箇所
function getAllCharsAfter(str, search) {
  const results = [];
  let pos = 0;
  while (true) {
    const index = str.indexOf(search, pos);
    if (index === -1) break;
    const after = index + search.length;
    if (after < str.length) results.push(str[after]);
    pos = after;
  }
  return results;
}

console.log(getAllCharsAfter("x-a-b-c", "-")); // ["a", "b", "c"]
g フラグ付き正規表現と matchAll() でも全マッチを取得できます。区切り文字で分割したいだけならsplit() で配列に変換する方法も便利です。
matchAll() で全マッチ
const str = "x-a-b-c";
const chars = [...str.matchAll(/-(.)/g)].map(m => m[1]);
console.log(chars); // ["a", "b", "c"]

エッジケースと注意点

検索文字列が末尾にある場合

検索文字列が文字列の末尾にあると、「直後の文字」は存在しません。

末尾にある場合
const str = "Hello";
const index = str.indexOf("llo"); // 2
const after = index + "llo".length; // 5(= str.length)

console.log(str[after]);                       // undefined(後ろに文字なし)
console.log(after < str.length ? str[after] : "後ろに文字なし"); // "後ろに文字なし"

絵文字・サロゲートペアの直後を扱う

str[index] はUTF-16のコード単位で文字を数えるため、直後の文字が絵文字(😀 など)の場合、半端な片割れ(文字化け)を返してしまいます。コードポイント単位で扱うには [...str]Array.from() で配列化します。

絵文字の直後対策
// ":" の直後が絵文字(\u{1F600} = 絵文字1文字)
const str = "id:\u{1F600}done";

console.log(str[str.indexOf(":") + 1]); // 壊れた半端な文字(絵文字の前半)

// 対策: コードポイント単位で扱う
const chars = [...str];
const ci = chars.indexOf(":");
console.log(chars[ci + 1]); // 絵文字1文字(壊れない)

方法の使い分け

方法 向いているケース 末尾にあるとき
indexOf() + str[i+len] 最も基本・高速。境界を自分で制御したい -1と区別して安全に扱える
slice(after, after + n) 直後のN文字をまとめて取りたい 空文字が返る(安全)
正規表現 (.) / lookbehind パターンで柔軟に・全マッチを扱いたい マッチしない(未検出と同じ扱い)
lastIndexOf() 最後の出現の直後だけが欲しい -1と区別して扱える
迷ったら indexOf() + 境界チェックが確実です。「直後に文字が無い」ケースまできちんと区別できるのは indexOf() 方式の利点です。

よくある実用例

key=value から値の先頭文字を取得する

値の先頭文字
function getValueInitial(line, key) {
  const sep = key + "=";
  const index = line.indexOf(sep);
  if (index === -1) return null;
  const after = index + sep.length;
  return after < line.length ? line[after] : null;
}

console.log(getValueInitial("name=Tanaka;age=30", "name")); // "T"
console.log(getValueInitial("name=Tanaka;age=30", "age"));  // "3"

ファイル名から拡張子の先頭文字を取得する

拡張子は「最後のドットの直後」なので lastIndexOf(".") を使います。なお「ドット以降をまるごと削除(拡張子を除く)」したい場合は指定した文字以降の文字列を削除する方法が便利です。

拡張子の先頭文字
function getExtensionInitial(filename) {
  const dot = filename.lastIndexOf(".");
  if (dot === -1 || dot + 1 >= filename.length) return null;
  return filename[dot + 1];
}

console.log(getExtensionInitial("report.pdf"));     // "p"
console.log(getExtensionInitial("photo.final.jpg")); // "j"
console.log(getExtensionInitial("noext"));           // null

よくある質問(FAQ)

Q検索文字列が末尾にあるとき、直後の文字はどうなりますか?
A直後に文字が存在しないため str[i + search.length]undefined を返します。after < str.length を判定し、超える場合は null や「後ろに文字なし」などを返すようにすると安全です。正規表現の (.) や lookbehind はこの場合マッチしません。
QindexOf() と正規表現はどちらを使うべきですか?
A単純に「特定文字列の直後の1文字」を取るだけなら indexOf() + ブラケット記法が最速かつ確実です。「直後に文字が無い」ケースを「見つからない」と区別できるのも indexOf() 方式の利点です。パターンマッチや全出現の一括取得が必要なときに正規表現(matchAll() など)を使うとよいでしょう。
Q検索文字列を変数で正規表現に渡すと動きません。
A検索文字列に . ( ? など正規表現の特殊文字が含まれていると誤動作や SyntaxError の原因になります。escapeRegExp() でエスケープしてから new RegExp() に渡してください。
Q直後の文字が絵文字だと文字化けします。
Astr[index]charAt() はUTF-16のコード単位で動くため、絵文字(サロゲートペア)を途中で切ると半端な文字になります。[...str]Array.from(str) でコードポイント単位の配列に変換してから扱うと崩れません。

まとめ

JavaScriptで特定の文字列の直後にある1文字を取得する方法のポイントを整理します。

  • 基本は indexOf() で位置を求め、str[index + search.length] で直後の文字を取得
  • 「見つからない(-1)」と「末尾にあって後ろに文字が無い」を必ず区別する
  • 直後のN文字は slice(after, after + n)、最後の出現は lastIndexOf()
  • 全出現の一括取得は indexOf() ループか matchAll()
  • 変数を正規表現に使うときはエスケープ、絵文字は [...str] でコードポイント単位に

対になる直前にある1文字を抽出する方法や、開始位置と文字数を指定して取得する方法indexOf() で文字列を検索する方法もあわせて確認すると理解が深まります。