JavaScriptで「特定の文字列の直前にある1文字を取得したい」と思ったことはありませんか?
たとえば、URLのパラメータ区切り文字の前の文字を調べたい、CSVデータの特定カラム手前の区切り文字を判定したい、ログファイルからエラーコード直前の識別子を抽出したいなど、文字列の「1つ前」を取得する処理は実務で頻繁に登場します。
この記事では、indexOf() による基本手法から正規表現(先読み・後読み)、split() の活用、lastIndexOf() による末尾検索、複数マッチの一括取得、さらに実務で使えるユーティリティ関数やパフォーマンス比較まで、あらゆるアプローチを網羅的に解説します。
indexOf() を使った基本的な方法
最もシンプルで理解しやすいのが、indexOf() で検索文字列の位置を取得し、その1つ前のインデックスから文字を抽出する方法です。
基本構文
JavaScript
const str = "abcdefg";
const search = "d";
// 検索文字列の位置を取得
const index = str.indexOf(search);
// 1つ前の文字を取得(位置が1以上であることを確認)
if (index > 0) {
const prevChar = str[index - 1];
console.log(prevChar); // "c"
}
indexOf() のポイント
indexOf() は検索文字列が見つかった最初の位置(0始まり)を返す
- 見つからない場合は
-1 を返す
index > 0 のチェックにより、先頭文字の場合(前に文字がない)も安全に処理できる
str[index - 1] はブラケット記法で1文字を取得する(charAt() でも可)
安全なヘルパー関数として実装する
実務では毎回 indexOf() のチェックを書くのは冗長なので、関数化しておくと便利です。
JavaScript
/**
* 指定した検索文字列の直前の1文字を返す
* @param {string} str - 対象文字列
* @param {string} search - 検索文字列
* @returns {string|null} 直前の1文字(見つからない場合は null)
*/
function getCharBefore(str, search) {
const index = str.indexOf(search);
if (index <= 0) return null;
return str[index - 1];
}
// 使用例
console.log(getCharBefore("Hello World", "World")); // " "(スペース)
console.log(getCharBefore("data-value", "value")); // "-"
console.log(getCharBefore("abcdef", "a")); // null(先頭のため)
console.log(getCharBefore("abcdef", "z")); // null(見つからない)
複数文字の検索文字列にも対応
indexOf() は1文字だけでなく複数文字の文字列も検索できるため、特別な変更なくそのまま動作します。
JavaScript
const url = "https://example.com?page=1&sort=name";
// "sort" の直前の1文字を取得
const idx = url.indexOf("sort");
if (idx > 0) {
console.log(url[idx - 1]); // "&"
}
charAt() を使う方法
charAt() は str[index] のブラケット記法と同じく、指定した位置の文字を返すメソッドです。古いブラウザとの互換性が必要な場合に使われます。
JavaScript
const str = "JavaScript";
const search = "Script";
const index = str.indexOf(search);
if (index > 0) {
// charAt() で1つ前の文字を取得
const prevChar = str.charAt(index - 1);
console.log(prevChar); // "a"
}
charAt() と ブラケット記法の違い
| 比較項目 |
str[index] |
str.charAt(index) |
| 範囲外アクセス |
undefined |
""(空文字列) |
| 戻り値の型 |
string または undefined |
常に string |
| IE対応 |
IE8以前は非対応 |
全バージョン対応 |
| 推奨度 |
モダン環境ではこちらが主流 |
レガシー互換が必要な場合 |
ポイント:モダンなJavaScript開発では str[index] のブラケット記法が一般的です。ただし、範囲外アクセス時の挙動が異なるため、エラーハンドリングの方法に注意してください。
正規表現を使った方法
正規表現を使うと、パターンマッチングによって柔軟に「特定文字列の1つ前」を取得できます。
キャプチャグループで取得する
検索文字列の直前の1文字をキャプチャグループ () で囲んで取得します。
JavaScript
const str = "abcdefg";
const search = "d";
// 任意の1文字(.)をキャプチャし、その直後に search が続くパターン
const regex = new RegExp(`(.)${search}`);
const match = str.match(regex);
if (match) {
console.log(match[1]); // "c"(キャプチャグループ1)
}
正規表現パターンの解説
(.) – 任意の1文字をキャプチャグループで取得
d(search変数)- 直後に続く検索文字列
match[0] – マッチ全体("cd")
match[1] – キャプチャグループ1("c" = 直前の1文字)
肯定先読み(Lookahead)を使う方法
肯定先読み (?=...) を使うと、検索文字列自体を消費せずに直前の文字だけを取得できます。
JavaScript
const str = "price:100,tax:10,total:110";
// ":" の直前の1文字を先読みで取得(最初のマッチ)
const match = str.match(/(.(?=:))/);
if (match) {
console.log(match[1]); // "e"(price の最後の文字)
}
肯定後読み(Lookbehind)で検索文字列の後ろを取得
ES2018 で追加された後読み(Lookbehind)を使えば、逆に「検索文字列の直後の1文字」を取得する際に便利です。ここでは参考として紹介しますが、「直前の1文字」には先読みのほうが適切です。
JavaScript(参考:後読みの例)
const str = "price:100";
// ":" の直後の1文字を後読みで取得
const match = str.match(/(?<=:)(.)/);
if (match) {
console.log(match[1]); // "1"(":" の直後)
}
注意:後読み(Lookbehind)は ES2018(ES9)で追加された機能です。IE11では対応していません。モダンブラウザ(Chrome 62+、Firefox 78+、Safari 16.4+)では問題なく使用できます。
正規表現の特殊文字をエスケープする
検索文字列に正規表現の特殊文字(. * + ? ^ $ { } ( ) | [ ] \ など)が含まれる場合は、エスケープが必要です。
JavaScript
/**
* 正規表現の特殊文字をエスケープする
*/
function escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// "(" を含む検索文字列
const str = "func(arg)";
const search = "(arg)";
const escaped = escapeRegex(search);
const regex = new RegExp(`(.)${escaped}`);
const match = str.match(regex);
if (match) {
console.log(match[1]); // "c"("func" の最後の文字)
}
注意:ユーザー入力を正規表現に組み込む場合は、必ずエスケープ処理を行ってください。エスケープしないと、意図しないパターンマッチや正規表現エラー(SyntaxError)が発生する原因になります。
split() を使った方法
split() で検索文字列を区切り文字として分割し、最初の部分文字列の末尾1文字を取得する方法です。
基本的な使い方
JavaScript
const str = "user-name-value";
const search = "name";
// search で分割し、最初の要素の末尾1文字を取得
const parts = str.split(search);
if (parts[0].length > 0) {
const prevChar = parts[0].slice(-1);
console.log(prevChar); // "-"
}
split() の仕組み
"user-name-value".split("name") → ["user-", "-value"]
- 最初の要素
"user-" の末尾1文字 → "-"
slice(-1) は文字列の末尾1文字を返す便利なメソッド
split() で全出現箇所の直前文字を取得
split() の結果を活用すれば、最初だけでなく全出現箇所の直前文字を一度に取得できます。
JavaScript
const str = "aXbXcXd";
const search = "X";
const parts = str.split(search);
// 最初の要素以外の各要素について、前の要素の末尾1文字が「直前の文字」
const prevChars = parts
.slice(0, -1) // 最後の要素を除外
.map(part => part.slice(-1))
.filter(ch => ch.length > 0); // 先頭にsearchがある場合を除外
console.log(prevChars); // ["a", "b", "c"]
lastIndexOf() で末尾から検索する方法
文字列中に検索文字列が複数回出現する場合、最後の出現箇所の直前1文字を取得したいケースがあります。その場合は lastIndexOf() を使います。
基本的な使い方
JavaScript
const str = "img_001.thumb.jpg";
const search = ".";
// 最後のドット(.)の直前1文字を取得
const lastIdx = str.lastIndexOf(search);
if (lastIdx > 0) {
const prevChar = str[lastIdx - 1];
console.log(prevChar); // "b"(.jpg の前 → thumb の "b")
}
indexOf() と lastIndexOf() の使い分け
| メソッド |
検索方向 |
返す位置 |
ユースケース |
indexOf() |
先頭 → 末尾 |
最初の出現位置 |
最初のマッチを処理したい場合 |
lastIndexOf() |
末尾 → 先頭 |
最後の出現位置 |
拡張子取得、末尾区切り文字の処理 |
実用例:ファイルの拡張子の直前の文字を取得
JavaScript
function getCharBeforeExtension(filename) {
const dotIndex = filename.lastIndexOf(".");
if (dotIndex <= 0) return null;
return filename[dotIndex - 1];
}
console.log(getCharBeforeExtension("report.pdf")); // "t"
console.log(getCharBeforeExtension("archive.tar.gz")); // "r"(.gz の前)
console.log(getCharBeforeExtension(".gitignore")); // null(ドットが先頭)
console.log(getCharBeforeExtension("noextension")); // null(ドットなし)
全出現箇所の直前文字を一括取得する方法
検索文字列が文字列中に複数回出現する場合に、すべての出現箇所の直前1文字をまとめて取得する方法を解説します。
indexOf() のループで全箇所を取得
JavaScript
function getAllCharsBefore(str, search) {
const results = [];
let pos = 0;
while (true) {
const index = str.indexOf(search, pos);
if (index === -1) break;
if (index > 0) {
results.push({
char: str[index - 1],
position: index - 1,
matchAt: index
});
}
pos = index + search.length;
}
return results;
}
const str = "the cat sat on the mat";
const results = getAllCharsBefore(str, "at");
results.forEach(r => {
console.log(`位置${r.matchAt}: 直前の文字 = "${r.char}"`);
});
実行結果
位置5: 直前の文字 = "c"
位置9: 直前の文字 = "s"
位置20: 直前の文字 = "m"
正規表現の g フラグで全マッチ取得
JavaScript
const str = "the cat sat on the mat";
// matchAll() で全マッチのイテレータを取得
const matches = [...str.matchAll(/(.)at/g)];
matches.forEach(m => {
console.log(`マッチ: "${m[0]}" → 直前の文字: "${m[1]}"`);
});
実行結果
マッチ: "cat" → 直前の文字: "c"
マッチ: "sat" → 直前の文字: "s"
マッチ: "mat" → 直前の文字: "m"
ポイント:matchAll() は ES2020 で追加されたメソッドです。g フラグ付きの正規表現で全マッチの詳細情報(キャプチャグループ含む)を取得できます。
slice() / substring() で直前の複数文字を取得する
ここまでは「1つ前の1文字」を取得する方法を解説してきましたが、実務では直前のN文字を取得したいケースもあります。slice() や substring() を使えば柔軟に対応できます。
直前のN文字を取得する
JavaScript
/**
* 検索文字列の直前N文字を取得する
* @param {string} str - 対象文字列
* @param {string} search - 検索文字列
* @param {number} n - 取得する文字数(デフォルト: 1)
* @returns {string|null}
*/
function getCharsBefore(str, search, n = 1) {
const index = str.indexOf(search);
if (index <= 0) return null;
// 取得開始位置(0未満にならないよう制限)
const start = Math.max(0, index - n);
return str.slice(start, index);
}
console.log(getCharsBefore("Hello World", "World", 1)); // " "
console.log(getCharsBefore("Hello World", "World", 3)); // "lo "
console.log(getCharsBefore("Hello World", "World", 10)); // "Hello "(最大6文字まで)
slice() と substring() の違い
| 比較項目 |
slice() |
substring() |
| 負のインデックス |
対応(末尾から数える) |
0として扱う |
| 引数の順序逆転 |
空文字列を返す |
自動的に入れ替える |
| 推奨度 |
モダンJSではこちらが主流 |
レガシーコードで見かける |
実務で使えるユーティリティ関数集
ここまでの知識を活かして、実務で即使える汎用的なユーティリティ関数をまとめます。
汎用ヘルパー関数
string-utils.js
/**
* 文字列操作ユーティリティ
*/
const StringUtils = {
/** 最初の出現箇所の直前1文字を取得 */
getCharBefore(str, search) {
const idx = str.indexOf(search);
return idx > 0 ? str[idx - 1] : null;
},
/** 最後の出現箇所の直前1文字を取得 */
getCharBeforeLast(str, search) {
const idx = str.lastIndexOf(search);
return idx > 0 ? str[idx - 1] : null;
},
/** 全出現箇所の直前1文字を配列で取得 */
getAllCharsBefore(str, search) {
const results = [];
let pos = 0;
while (true) {
const idx = str.indexOf(search, pos);
if (idx === -1) break;
if (idx > 0) results.push(str[idx - 1]);
pos = idx + search.length;
}
return results;
},
/** N番目の出現箇所の直前1文字を取得 */
getCharBeforeNth(str, search, n = 1) {
let pos = 0;
let count = 0;
while (true) {
const idx = str.indexOf(search, pos);
if (idx === -1) return null;
count++;
if (count === n) {
return idx > 0 ? str[idx - 1] : null;
}
pos = idx + search.length;
}
}
};
// 使用例
const csv = "name,age,city,country";
console.log(StringUtils.getCharBefore(csv, ",")); // "e"
console.log(StringUtils.getCharBeforeLast(csv, ",")); // "y"
console.log(StringUtils.getAllCharsBefore(csv, ",")); // ["e", "e", "y"]
console.log(StringUtils.getCharBeforeNth(csv, ",", 2)); // "e"(2番目のカンマの前)
実行結果
e
y
["e", "e", "y"]
e
実務例1:URLパラメータの区切り文字判定
JavaScript
/**
* URLにクエリパラメータを安全に追加する
* ?がすでにあれば&で、なければ?で追加
*/
function addQueryParam(url, key, value) {
const separator = url.includes("?") ? "&" : "?";
return `${url}${separator}${key}=${encodeURIComponent(value)}`;
}
/**
* 特定パラメータの直前の区切り文字を確認する実例
*/
function getParamSeparator(url, paramName) {
const idx = url.indexOf(paramName);
if (idx <= 0) return null;
const prevChar = url[idx - 1];
return prevChar; // "?" or "&"
}
const url1 = "https://example.com?page=1&sort=name";
console.log(getParamSeparator(url1, "page")); // "?"
console.log(getParamSeparator(url1, "sort")); // "&"
実務例2:CSVパーサーでの区切り文字検出
JavaScript
/**
* CSVの各フィールドの直前の文字を分析し、
* クォートで囲まれたフィールドかどうかを判定
*/
function analyzeCSVField(csvLine, fieldValue) {
const idx = csvLine.indexOf(fieldValue);
if (idx <= 0) return { found: false };
const prevChar = csvLine[idx - 1];
return {
found: true,
isQuoted: prevChar === '\"',
isFirstField: idx === 0,
prevChar: prevChar
};
}
const line = 'name,"Tokyo, Japan",25';
console.log(analyzeCSVField(line, "Tokyo"));
実行結果
{ found: true, isQuoted: true, isFirstField: false, prevChar: '"' }
実務例3:ログ解析でエラーレベルの直前文字を取得
JavaScript
/**
* ログ行からエラーレベルタグの直前文字を取得し、
* ログフォーマットの種類を判定する
*/
function detectLogFormat(logLine) {
const levels = ["ERROR", "WARN", "INFO", "DEBUG"];
for (const level of levels) {
const idx = logLine.indexOf(level);
if (idx > 0) {
const prev = logLine[idx - 1];
if (prev === "[") return "bracket-format"; // [ERROR]
if (prev === " ") return "space-separated"; // ... ERROR ...
if (prev === "|") return "pipe-separated"; // ...|ERROR|...
}
}
return "unknown";
}
console.log(detectLogFormat("2024-01-15 [ERROR] DB connection failed"));
console.log(detectLogFormat("2024-01-15 ERROR DB connection failed"));
console.log(detectLogFormat("2024-01-15|ERROR|DB connection failed"));
実行結果
bracket-format
space-separated
pipe-separated
エッジケースと注意点
「直前の1文字を取得する」処理には、いくつかの落とし穴があります。実務でバグを防ぐために必ず押さえておきましょう。
検索文字列が先頭にある場合
検索文字列が文字列の先頭にある場合、「1つ前の文字」は存在しません。
JavaScript
const str = "Hello World";
// "Hello" は先頭にあるため、直前の文字は存在しない
const idx = str.indexOf("Hello"); // 0
// 危険な実装(index - 1 が -1 になる)
console.log(str[idx - 1]); // undefined
// 安全な実装
if (idx > 0) {
console.log(str[idx - 1]);
} else {
console.log("直前の文字は存在しません");
}
実行結果
undefined
直前の文字は存在しません
検索文字列が見つからない場合
JavaScript
const str = "abcdef";
const idx = str.indexOf("xyz"); // -1
// 危険! str[-2] は undefined だがエラーにはならない
console.log(str[idx - 1]); // undefined
// 必ず indexOf の結果をチェックしてから使う
if (idx !== -1 && idx > 0) {
console.log(str[idx - 1]);
} else {
console.log("検索文字列が見つかりません");
}
実行結果
undefined
検索文字列が見つかりません
空文字列の扱い
JavaScript
// 空文字列で検索すると indexOf は 0 を返す
console.log("abc".indexOf("")); // 0
// 対象文字列が空の場合
console.log("".indexOf("a")); // -1
// 安全な関数は両方のケースを考慮する
function safeGetCharBefore(str, search) {
if (!str || !search) return null;
const idx = str.indexOf(search);
return idx > 0 ? str[idx - 1] : null;
}
サロゲートペア(絵文字・特殊文字)の問題
JavaScriptの文字列はUTF-16でエンコードされるため、絵文字や一部の漢字は2つのコードユニット(サロゲートペア)で表現されます。
JavaScript
const str = "I like cats";
// 絵文字はサロゲートペア(2コードユニット)
console.log(str.length); // 13("I like " = 7, 絵文字 = 2, "cats" = 4)
console.log(str[7]); // サロゲートペアの前半(文字化け)
// indexOf は正しく動作する
const idx = str.indexOf("cats"); // 9
console.log(str[idx - 1]); // サロゲートペアの後半(文字化け!)
// 対策: Array.from() で正しく文字単位に分割
const chars = Array.from(str);
const catIdx = chars.findIndex((_, i) =>
chars.slice(i, i + 4).join("") === "cats"
);
if (catIdx > 0) {
console.log(chars[catIdx - 1]); // 正しく絵文字全体を取得
}
注意:絵文字や特殊文字を含む文字列を扱う場合は、Array.from() や [...str](スプレッド構文)で正しい文字単位に分割してから処理してください。str[index] や charAt() はコードユニット単位のため、サロゲートペアの半分だけを返す可能性があります。
大文字・小文字を区別しない検索
JavaScript
/**
* 大文字・小文字を区別せずに直前の1文字を取得
*/
function getCharBeforeCI(str, search) {
// 方法1: toLowerCase() で揃える
const idx = str.toLowerCase().indexOf(search.toLowerCase());
return idx > 0 ? str[idx - 1] : null;
}
/**
* 正規表現の i フラグを使う方法
*/
function getCharBeforeCIRegex(str, search) {
const escaped = search.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const match = str.match(new RegExp(`(.)${escaped}`, "i"));
return match ? match[1] : null;
}
console.log(getCharBeforeCI("Hello WORLD", "world")); // " "
console.log(getCharBeforeCIRegex("Hello WORLD", "world")); // " "
よくあるエラーと対処法
| エラー・症状 |
原因 |
対処法 |
undefined が返る |
indexOfが-1を返した(見つからない)、または先頭にマッチした |
index > 0 のチェックを必ず行う |
| サロゲートペアの半分が返る |
絵文字の直前で str[index] を使用 |
Array.from(str) で分割してから操作 |
SyntaxError: Invalid regular expression |
特殊文字を含む検索文字列を正規表現に直接使用 |
escapeRegex() でエスケープしてから new RegExp() に渡す |
| 期待と違う文字が返る |
複数回出現するが最初のマッチを取得している |
lastIndexOf() や getAllCharsBefore() を使う |
TypeError: Cannot read properties of null |
正規表現の match() が null を返した |
if (match) のnullチェックを必ず行う |
エラーを防ぐ堅牢な実装パターン
JavaScript(堅牢な実装例)
/**
* 堅牢版:あらゆるエッジケースに対応した直前文字取得
* @param {string} str - 対象文字列
* @param {string} search - 検索文字列
* @param {Object} options - オプション
* @param {boolean} options.caseInsensitive - 大文字小文字を無視
* @param {number} options.occurrence - N番目の出現(デフォルト: 1)
* @param {boolean} options.fromEnd - 末尾から検索
* @returns {string|null}
*/
function getCharBeforeRobust(str, search, options = {}) {
// 入力バリデーション
if (typeof str !== "string" || typeof search !== "string") {
return null;
}
if (str.length === 0 || search.length === 0) {
return null;
}
const {
caseInsensitive = false,
occurrence = 1,
fromEnd = false
} = options;
// 末尾から検索
if (fromEnd) {
const s = caseInsensitive ? str.toLowerCase() : str;
const q = caseInsensitive ? search.toLowerCase() : search;
const idx = s.lastIndexOf(q);
return idx > 0 ? str[idx - 1] : null;
}
// N番目の出現を検索
const s = caseInsensitive ? str.toLowerCase() : str;
const q = caseInsensitive ? search.toLowerCase() : search;
let pos = 0;
let count = 0;
while (pos < s.length) {
const idx = s.indexOf(q, pos);
if (idx === -1) break;
count++;
if (count === occurrence) {
return idx > 0 ? str[idx - 1] : null;
}
pos = idx + q.length;
}
return null;
}
// 使用例
const text = "aXbXcXd";
console.log(getCharBeforeRobust(text, "X")); // "a"
console.log(getCharBeforeRobust(text, "X", { occurrence: 2 })); // "b"
console.log(getCharBeforeRobust(text, "X", { fromEnd: true })); // "c"
console.log(getCharBeforeRobust(text, "x", { caseInsensitive: true })); // "a"
各方法のパフォーマンス比較
どの方法を選ぶべきか迷った場合に参考になるパフォーマンス比較です。10万回の繰り返し処理で計測した結果を示します。
JavaScript(ベンチマーク)
const str = "abcdefghijklmnopqrstuvwxyz".repeat(100);
const search = "xyz";
const ITERATIONS = 100000;
// indexOf
console.time("indexOf");
for (let i = 0; i < ITERATIONS; i++) {
const idx = str.indexOf(search);
if (idx > 0) str[idx - 1];
}
console.timeEnd("indexOf");
// 正規表現
console.time("regex");
const regex = new RegExp(`(.)${search}`);
for (let i = 0; i < ITERATIONS; i++) {
const m = str.match(regex);
if (m) m[1];
}
console.timeEnd("regex");
// split
console.time("split");
for (let i = 0; i < ITERATIONS; i++) {
const parts = str.split(search);
if (parts[0].length > 0) parts[0].slice(-1);
}
console.timeEnd("split");
実行結果(参考値)
indexOf: 12ms
regex: 45ms
split: 380ms
パフォーマンスのポイント
indexOf() が圧倒的に高速(文字列探索に最適化されている)
- 正規表現はパターンコンパイルのオーバーヘッドがある(ループ外で
new RegExpを1回だけ実行すると改善)
split() は文字列全体を分割するため、長い文字列では遅くなる
- 通常の用途ではどの方法でも十分高速なので、可読性を優先して選択するのがベスト
方法別の比較表
各方法の特徴を一覧で比較します。用途に応じて最適な方法を選んでください。
| 方法 |
速度 |
柔軟性 |
複数マッチ |
推奨用途 |
indexOf() + str[] |
最速 |
低 |
ループで可能 |
シンプルな単一マッチ |
charAt() |
最速 |
低 |
ループで可能 |
レガシーブラウザ対応 |
正規表現 (.) |
中 |
高 |
matchAll() |
複雑なパターンマッチ |
先読み (?=...) |
中 |
最高 |
matchAll() |
検索文字列を消費せずに取得 |
split() |
遅 |
中 |
自動で全箇所 |
分割と同時に取得したい場合 |
lastIndexOf() |
最速 |
低 |
最後のみ |
末尾のマッチを処理 |
ポイント:迷ったら indexOf() を使いましょう。最もシンプルで高速、かつ可読性も高い方法です。正規表現は複雑なパターンが必要な場合にのみ使用するのがベストプラクティスです。
よくある質問(FAQ)
Q. 検索文字列の「1つ後の文字」を取得するにはどうすればよいですか?
indexOf() で見つかった位置に検索文字列の長さを足せば、直後の位置になります。
JavaScript
function getCharAfter(str, search) {
const idx = str.indexOf(search);
if (idx === -1) return null;
const afterIdx = idx + search.length;
return afterIdx < str.length ? str[afterIdx] : null;
}
console.log(getCharAfter("Hello World", "Hello")); // " "
console.log(getCharAfter("abc", "abc")); // null(後ろに文字がない)
Q. TypeScript で型安全に実装するにはどうすればよいですか?
TypeScript
function getCharBefore(str: string, search: string): string | null {
const index: number = str.indexOf(search);
if (index <= 0) return null;
return str[index - 1];
}
// 戻り値が string | null なので null チェックが必要
const result = getCharBefore("Hello", "llo");
if (result !== null) {
console.log(result.toUpperCase()); // "E"(型安全に操作可能)
}
Q. Node.js と ブラウザで動作に違いはありますか?
この記事で紹介した方法はすべてECMAScript標準に基づいているため、Node.js とブラウザで動作に違いはありません。ただし、以下の点に注意してください。
| 機能 |
必要なES仕様 |
対応ブラウザ |
Node.js |
indexOf() |
ES1 |
全ブラウザ |
全バージョン |
str[index] ブラケット記法 |
ES5 |
IE9+ |
全バージョン |
| テンプレートリテラル |
ES6 |
IE以外 |
4.0+ |
後読み (?<=...) |
ES2018 |
Chrome 62+, FF 78+, Safari 16.4+ |
8.10+ |
matchAll() |
ES2020 |
Chrome 73+, FF 67+, Safari 13+ |
12.0+ |
まとめ
この記事のポイント
- 基本:
indexOf() で位置を取得し、str[index - 1] で直前の1文字を取得するのが最もシンプル
- 末尾検索:最後の出現箇所が必要な場合は
lastIndexOf() を使用
- 正規表現:
(.) キャプチャグループや先読み (?=...) で柔軟なパターンマッチが可能
- 全マッチ:
matchAll() や indexOf() のループで全出現箇所の直前文字を取得
- split():分割結果の各要素の末尾文字 = 各出現箇所の直前文字
- エッジケース:先頭マッチ、未検出、空文字列、サロゲートペアに注意
- パフォーマンス:
indexOf() が最速。通常は可読性を優先して選択
ポイント:実務では入力バリデーション(null/undefined/空文字チェック)と境界条件(先頭マッチ・未検出)の処理を必ず入れましょう。堅牢な関数を一度作っておけば、プロジェクト全体で安全に再利用できます。
関連記事