目次
概要
文字列から特定の部分を抽出する処理は、IDの解析、日付フォーマットの整形、個人情報のマスキングなどで頻繁に使用されます。 JavaScriptには slice() と substring() という似た機能を持つメソッドが存在しますが、特に「負の数」を指定した際の挙動が大きく異なります。 本記事では、実務で推奨される slice() を中心に、両者の違いと正確な使い分けをコードベースで解説します。
仕様(入出力)
- 入力: 形式が決まっている商品シリアルコード(例:
PRD-2026-X99)。 - 出力: コードから切り出した「製造年」「識別ID」、および末尾から抽出した「バージョン情報」。
- 動作: 入力されたシリアルコードを解析し、
sliceとsubstringそれぞれの結果の違いを画面に表示して比較する。
基本の使い方
開始位置(0始まり)と終了位置(その文字を含まない)を指定して文字列を切り出します。
const text = "Environment";
// どちらも結果は同じ "viron" (2文字目から7文字目の手前まで)
const resSlice = text.slice(2, 7);
const resSub = text.substring(2, 7);
// 引数が1つの場合、そこから末尾まで
// 結果: "ronment"
const resEnd = text.slice(4);
コード全文(HTML / JavaScript)
HTML
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>String Slice vs Substring</title>
<style>
body { font-family: sans-serif; padding: 20px; line-height: 1.6; }
.container { max-width: 600px; margin: 0 auto; }
.input-group { margin-bottom: 20px; }
input { padding: 8px; width: 60%; font-family: monospace; }
button { padding: 8px 16px; cursor: pointer; }
.result-box {
background: #f4f4f4;
border: 1px solid #ccc;
padding: 15px;
border-radius: 5px;
margin-top: 10px;
}
.code-label { font-weight: bold; color: #333; display: block; margin-top: 10px; }
.output-val { font-family: monospace; color: #d32f2f; background: #fff; padding: 2px 5px; }
.desc { font-size: 0.9em; color: #666; }
</style>
</head>
<body>
<div class="container">
<h2>シリアルコード解析</h2>
<p>推奨フォーマット: <code>ABC-1234-XYZ</code> (12文字以上)</p>
<div class="input-group">
<input type="text" id="serialInput" value="ABC-1234-XYZ">
<button id="parseBtn">解析実行</button>
</div>
<div id="resultArea" class="result-box">
ここに結果が表示されます
</div>
</div>
<script src="app.js"></script>
</body>
</html>
JavaScript
/**
* HTML要素の参照取得
*/
const serialInput = document.getElementById('serialInput');
const parseBtn = document.getElementById('parseBtn');
const resultArea = document.getElementById('resultArea');
/**
* 文字列操作の結果をHTMLとして生成するヘルパー関数
* @param {string} label - ラベル名
* @param {string} method - 使用メソッドの文字列表現
* @param {string} result - 切り出し結果
* @param {string} description - 挙動の説明
* @returns {string} HTML文字列
*/
const createResultHtml = (label, method, result, description) => {
return `
<div>
<span class="code-label">${label}</span>
<code>${method}</code> → <span class="output-val">"${result}"</span><br>
<span class="desc">※ ${description}</span>
</div>
`;
};
/**
* 解析処理の実行
*/
const parseSerialCode = () => {
const text = serialInput.value;
let htmlContent = '';
// 1. 基本的な切り出し(違いなし)
// 4文字目から8文字目の手前までを取り出す
const basicSlice = text.slice(4, 8);
const basicSub = text.substring(4, 8);
htmlContent += `<h3>1. 基本的な範囲指定 (4, 8)</h3>`;
htmlContent += createResultHtml('Slice', `text.slice(4, 8)`, basicSlice, '指定範囲を抽出');
htmlContent += createResultHtml('Substring', `text.substring(4, 8)`, basicSub, '指定範囲を抽出(挙動は同じ)');
// 2. 負の値の扱い(決定的な違い)
// 末尾から3文字を取り出す意図
const negativeSlice = text.slice(-3);
const negativeSub = text.substring(-3);
htmlContent += `<h3>2. 負の値を指定 (-3)</h3>`;
htmlContent += createResultHtml('Slice', `text.slice(-3)`, negativeSlice, '末尾から3文字を抽出(成功)');
htmlContent += createResultHtml('Substring', `text.substring(-3)`, negativeSub, '負の値は0とみなされ全文字返却(失敗)');
// 3. 開始 > 終了 の逆転指定(仕様の違い)
// (8, 4) と指定した場合
const swapSlice = text.slice(8, 4);
const swapSub = text.substring(8, 4);
htmlContent += `<h3>3. 開始 > 終了 の指定 (8, 4)</h3>`;
htmlContent += createResultHtml('Slice', `text.slice(8, 4)`, swapSlice, '空文字が返る(開始位置が終了位置より後のため)');
htmlContent += createResultHtml('Substring', `text.substring(8, 4)`, swapSub, '自動的に(4, 8)に入れ替えて処理される');
// 結果を描画
resultArea.innerHTML = htmlContent;
};
// ボタンクリック時に実行
parseBtn.addEventListener('click', parseSerialCode);
カスタムポイント
- ID解析ロジック: 実務では固定のインデックス(
4, 8など)ではなく、ハイフンの位置を動的に特定してから切り出す実装が堅牢です。JavaScriptconst firstHyphen = text.indexOf('-'); const target = text.slice(firstHyphen + 1); - マスキング処理: 個人情報の表示において、
sliceは「末尾4桁だけ残す」といった処理に最適です。JavaScriptconst visible = text.slice(-4); // 下4桁 const masked = "****-****-" + visible;
注意点
- 負の値(マイナス)の挙動の違い:
- slice: 「末尾からの文字数」として扱います。
-1は最後の文字です。 - substring: 「0」として扱います。つまり先頭からの開始となります。 この違いがあるため、特別な理由がない限り配列操作メソッドと挙動が統一されている
sliceの使用が推奨されます。
- slice: 「末尾からの文字数」として扱います。
- 引数の入れ替え:
substring(start, end)はstart > endの場合に自動で数値を入れ替えますが、sliceは入れ替えを行わず空文字を返します。意図しない入力を検知したい場合、sliceの方がバグに気づきやすい利点があります。
応用
拡張子の除去(末尾からの切り出し)
ファイル名から拡張子を除いたファイル名本体のみを取得する場合、slice と lastIndexOf を組み合わせると簡潔に書けます。
const filename = "document.ver2.pdf";
// 最後のドットの位置を見つける
const lastDotIndex = filename.lastIndexOf(".");
// 先頭から最後のドットまでを切り出す
// ドットが見つからない場合はそのまま返す
const nameOnly = lastDotIndex !== -1 ? filename.slice(0, lastDotIndex) : filename;
console.log(nameOnly); // "document.ver2"
まとめ
slice: 負の数に対応(末尾からカウント)。配列のsliceと同じ挙動。現代のJavaScript開発では基本的にこちらを使用します。substring: 負の数は0扱い。引数の大小を自動補正する機能がありますが、これが予期せぬ挙動を生む場合もあります。- 「後ろから〇文字」を取得したい場合は
slice一択です。
