JavaScriptで「特定の文字列のすぐ後ろにある1文字を取り出したい」という場面はよくあります。たとえば key=value の = の直後(値の先頭文字)を調べたり、ファイル名の . の直後(拡張子の先頭)を取得したりするケースです。
基本は「indexOf() で検索文字列の位置を求め、その文字数ぶん後ろの位置を見る」という流れになります。この記事では indexOf()・slice()・正規表現(lookbehind)の3通りを比較しながら、「検索文字列が末尾にあって後ろに文字が無い」「見つからない」「絵文字で文字化けする」といった落とし穴の対策まで解説します。
indexOf() で位置を取得し、-1(見つからない)と位置 + 検索文字列の長さが文字列の末尾を超えていないかをチェックしてから str[i + search.length] で直後の文字を取り出すのが安全です。indexOf() で取得する(基本・推奨)
indexOf() で検索文字列の位置を取得し、そこに検索文字列の長さを足した位置が「直後の文字」のインデックスです。
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つを毎回チェックするのは冗長なので、関数化しておくと安全です。
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(見つからない)
indexOf() === -1)」と「検索文字列が末尾にあって後ろに文字が無い(after === str.length)」は別物です。どちらも処理しないと、後者で undefined が返ってバグの原因になります。slice() / charAt() で取得する
ブラケット記法 str[after] の代わりに、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() が便利です。開始位置と文字数の詳しい指定方法は開始位置と文字数を指定して取得する方法を参照してください。
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)で取得する
肯定後読み (?<=...) を使うと、検索文字列自体を結果に含めずに直後の文字だけを取得できます。
const str = "price:100"; // ":" の直後の1文字を後読みで取得 const match = str.match(/(?<=:)./); console.log(match ? match[0] : null); // "1"
indexOf() 方式が確実です)。最後の出現の直後を取得する(lastIndexOf)
検索文字列が複数回登場する文字列で「最後の出現の直後」を取りたい場合は、indexOf() の代わりに 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"
全出現箇所の直後文字を一括取得する
検索文字列が複数回出現する場合、すべての直後文字をまとめて取得できます。
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"]
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)
str[i + search.length] は undefined を返します。after < str.length を判定し、超える場合は null や「後ろに文字なし」などを返すようにすると安全です。正規表現の (.) や lookbehind はこの場合マッチしません。indexOf() + ブラケット記法が最速かつ確実です。「直後に文字が無い」ケースを「見つからない」と区別できるのも indexOf() 方式の利点です。パターンマッチや全出現の一括取得が必要なときに正規表現(matchAll() など)を使うとよいでしょう。. ( ? など正規表現の特殊文字が含まれていると誤動作や SyntaxError の原因になります。escapeRegExp() でエスケープしてから new RegExp() に渡してください。str[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() で文字列を検索する方法もあわせて確認すると理解が深まります。
