【JavaScript】String.repeat() メソッドの使い方|文字列の複製・プログレスバー・区切り線・パディングなど実践パターンまで解説

String.prototype.repeat(n) は文字列を指定した回数だけ繰り返した新しい文字列を返すメソッドです。ループや文字列結合を使わずに1行でシンプルに書けるため、コードが読みやすくなります。

単純に見えますが、引数に 0・負数・小数・Infinity を渡したときの挙動はそれぞれ異なります。また、プログレスバーや区切り線・インデント生成など、実務で役立つパターンも多数あります。この記事で repeat() の正確な動作と実践的な使い方を体系的に解説します。

スポンサーリンク

repeat() の基本構文と返り値

基本構文
// 構文: str.repeat(count)
// count: 繰り返す回数(0以上の整数)
// 返り値: 新しい文字列(元の文字列は変更されない)

console.log('ab'.repeat(3));   // 'ababab'
console.log('★'.repeat(5));   // '★★★★★'
console.log('ha'.repeat(4));   // 'hahahaha'
console.log('-'.repeat(20));   // '--------------------'

// 0を渡すと空文字を返す
console.log('abc'.repeat(0));  // ''

// 元の文字列は変更されない
const str = 'hello';
str.repeat(3);                 // 'hellohellohello'
console.log(str);              // 'hello'(元の文字列はそのまま)
repeat() はイミュータブルなメソッドです:repeat() は元の文字列を変更せず、新しい文字列を返します。結果を使いたい場合は変数に受け取ってください。

引数に 0・小数・負数・Infinity を渡したときの挙動

引数に整数以外を渡した場合、動作が異なります。事前に把握しておきましょう。

引数の値 動作
0 空文字を返す "ab".repeat(0)""
正の整数 n回繰り返した文字列を返す "ab".repeat(3)"ababab"
小数(正) 切り捨て(floor)して処理 "ab".repeat(2.9)"abab"
負の数 RangeError をスロー "ab".repeat(-1) → エラー
Infinity RangeError をスロー "ab".repeat(Infinity) → エラー
NaN 0 として扱う(空文字) "ab".repeat(NaN)""
引数の挙動の確認
console.log('x'.repeat(0));          // ''
console.log('x'.repeat(2.9));        // 'xx' (2.9 → 2 に切り捨て)
console.log('x'.repeat(NaN));        // ''  (NaN → 0 扱い)

try {
  'x'.repeat(-1);                    // RangeError: Invalid count value: -1
} catch (e) {
  console.error(e.message);
}

try {
  'x'.repeat(Infinity);              // RangeError: Invalid count value: Infinity
} catch (e) {
  console.error(e.message);
}
負数・Infinity は RangeError になる:動的に計算した値を repeat() に渡す場合は、事前に Math.max(0, Math.floor(n)) で安全な整数に変換してください。NaN0 扱いになりエラーにはなりませんが、意図しない空文字が返るため明示的なチェックを推奨します。
安全に repeat を呼び出す
/**
 * 引数を安全に正規化してから repeat する
 * @param {string} str
 * @param {number} n
 */
function safeRepeat(str, n) {
  const count = Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 0;
  return str.repeat(count);
}

console.log(safeRepeat('★', 3));    // '★★★'
console.log(safeRepeat('★', -1));   // ''(エラーにならない)
console.log(safeRepeat('★', Infinity)); // ''(エラーにならない)

Array(n).join() との比較(旧来の書き方)

repeat() が ES2015 で追加される前は、Array(n + 1).join(str) というトリッキーな書き方がよく使われていました。

Array(n).join() vs repeat()
// 旧来の書き方(ES2015以前)
const old = Array(5 + 1).join('-');  // '-----'
const old2 = new Array(4).join('ab'); // 'ababab'(要素数-1回)

// repeat() を使った現代の書き方
const modern  = '-'.repeat(5);       // '-----'
const modern2 = 'ab'.repeat(3);      // 'ababab'

// repeat() のほうが意図が明確で間違えにくい
// Array(n+1).join() の +1 は直感的でなく混乱しやすい
repeat() が推奨される理由:Array(n + 1).join(str) は「n+1 要素の空配列を str で繋ぐと n 個の str が並ぶ」という間接的な表現です。str.repeat(n) は意図が直接的で読みやすく、+1 の間違いも起きません。現代のコードでは repeat() を使ってください。

実践パターン集

テキストUIのプログレスバー

プログレスバーの生成
/**
 * テキストベースのプログレスバーを生成
 * @param {number} percent - 0〜100
 * @param {number} width   - バーの全幅(文字数)
 */
function progressBar(percent, width = 20) {
  const filled = Math.round((percent / 100) * width);
  const empty  = width - filled;
  return `[${'█'.repeat(filled)}${'░'.repeat(empty)}] ${percent}%`;
}

console.log(progressBar(0));    // '[░░░░░░░░░░░░░░░░░░░░] 0%'
console.log(progressBar(50));   // '[██████████░░░░░░░░░░] 50%'
console.log(progressBar(100));  // '[████████████████████] 100%'

// DOM に表示する場合
function renderProgress(el, percent) {
  el.textContent = progressBar(percent);
}

区切り線・罫線の生成

区切り線の生成
// シンプルな区切り線
const hr = '─'.repeat(40);
console.log(hr); // '────────────────────────────────────────'

// 見出し付き区切り線
function sectionTitle(title, width = 40) {
  const padding = Math.max(0, width - title.length - 2);
  const left    = Math.floor(padding / 2);
  const right   = padding - left;
  return `${'─'.repeat(left)} ${title} ${'─'.repeat(right)}`;
}

console.log(sectionTitle('設定'));
// '─────────────────── 設定 ──────────────────'

インデント・スペースの生成

インデント生成
// ツリー構造の表示
function treeItem(label, depth) {
  return '  '.repeat(depth) + '└─ ' + label;
}

console.log(treeItem('root', 0));     // '└─ root'
console.log(treeItem('child', 1));    // '  └─ child'
console.log(treeItem('grandchild', 2)); // '    └─ grandchild'

// JSON 風インデントの生成
function indent(depth, spaces = 2) {
  return ' '.repeat(spaces * depth);
}

星評価(★)の表示

星評価UIの生成
/**
 * 星評価のテキストを生成(0〜5)
 * @param {number} rating - 0〜5
 * @param {number} max    - 最大値(デフォルト5)
 */
function starRating(rating, max = 5) {
  const filled = Math.min(Math.max(0, Math.round(rating)), max);
  const empty   = max - filled;
  return '★'.repeat(filled) + '☆'.repeat(empty);
}

console.log(starRating(0));   // '☆☆☆☆☆'
console.log(starRating(3));   // '★★★☆☆'
console.log(starRating(5));   // '★★★★★'
console.log(starRating(4.6)); // '★★★★★'(4.6 → 5 に四捨五入)

マスク文字列の生成(パスワード・カード番号)

マスク処理
// パスワードの文字数をマスク
function maskPassword(password) {
  return '●'.repeat(password.length);
}
console.log(maskPassword('secret123')); // '●●●●●●●●●'

// クレジットカード番号の末尾4桁のみ表示
function maskCardNumber(cardNum) {
  const last4 = cardNum.slice(-4);
  return '*'.repeat(cardNum.length - 4) + last4;
}
console.log(maskCardNumber('1234567890123456')); // '************3456'

padStart・padEnd との使い分け

repeat()padStart()/padEnd() はどちらも文字を繰り返せますが、用途が異なります。

メソッド 目的
repeat(n) 文字列を n 回繰り返す "ab".repeat(3)"ababab"
padStart(n, ch) 文字列の先頭を ch で埋めて合計 n 文字にする "5".padStart(3, "0")"005"
padEnd(n, ch) 文字列の末尾を ch で埋めて合計 n 文字にする "5".padEnd(3, "0")"500"
repeat vs padStart の使い分け
// 「指定した回数だけ繰り返す」→ repeat
const loading = '.'.repeat(3);          // '...' (回数起点)

// 「指定した桁数に揃える(ゼロ埋め)」→ padStart
const zeroPad = String(5).padStart(3, '0'); // '005' (桁数起点)

// NG: repeat でゼロ埋めを無理に実現しようとすると冗長
const n = 5;
const bad = '0'.repeat(3 - String(n).length) + n; // '005'(動くが冗長)

// OK: padStart を使う
const good = String(n).padStart(3, '0'); // '005'(こちらが適切)
使い分けの基準:「N 回繰り返したい」→ repeat(N)。「合計 N 文字になるよう補填したい」→ padStart(N, char) / padEnd(N, char)。目的に合ったメソッドを使うとコードの意図が伝わりやすくなります。

配列と組み合わせたパターン

配列の各要素に repeat を適用
// グラフのバー描画
const data = [
  { label: 'JavaScript', score: 85 },
  { label: 'Python',     score: 72 },
  { label: 'Java',       score: 60 },
];

function barChart(items, maxWidth = 30) {
  const maxScore = Math.max(...items.map((d) => d.score));
  return items.map(({ label, score }) => {
    const barLen = Math.round((score / maxScore) * maxWidth);
    return `${label.padEnd(12)} ${'▓'.repeat(barLen)} ${score}`;
  }).join('\n');
}

console.log(barChart(data));
// JavaScript  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ 85
// Python      ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓     72
// Java        ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓       60

よくある質問

Qrepeat() に非常に大きな数を渡すとどうなりますか?
A生成される文字列のサイズが JavaScript エンジンの最大文字列長を超えると RangeError: Invalid string length になります。V8(Chrome・Node.js)では最大約 2^29 – 24(約 5 億文字)が上限です。実用上は数千〜数万回で問題になることはほぼありませんが、動的に計算した大きな数を渡す場合は注意してください。
Q空文字 “” に repeat() を使うとどうなりますか?
A空文字に repeat() を使うと常に空文字が返ります。"".repeat(100)"" です。エラーにはなりません。
Qrepeat() で生成した文字列を DOM に表示するときのパフォーマンスは問題ありませんか?
Arepeat() 自体は高速で、数百〜数千回の繰り返しでもほぼ問題ありません。DOM への反映は textContent で行えばブラウザが効率的に処理します。プログレスバーのように頻繁に更新する場合は、CSS の幅(width: %)を変更する方法のほうがGPU アクセラレーションが効いてスムーズです。
QTypeScript で repeat() の引数を型安全にするには?
ATypeScript では repeat(count: number) の型定義があります。number 型であれば型エラーにはなりませんが、ランタイムで負数・Infinity・NaN が渡る可能性がある場合はsafeRepeat() のようなラッパー関数を用意して前述の正規化処理を行うことを推奨します。
QString.prototype.repeat() の対応ブラウザは?
AES2015 で標準化されており、IE11 を除く現代のブラウザ・Node.js(v4 以降)すべてで使えます。IE11 のサポートが必要な場合は Array(n + 1).join(str) でポリフィルするか、core-js などのポリフィルライブラリを使ってください。現代の開発環境では IE11 非対応が一般的になっており、特別な理由がなければそのまま使えます。

まとめ

String.prototype.repeat() のポイントをまとめます。

  • str.repeat(n) は str を n 回繰り返した新しい文字列を返す(元の文字列は変わらない)
  • 0 は空文字、負数・Infinity は RangeError、小数は切り捨て、NaN は 0 扱い
  • 動的な値を渡す場合は Math.max(0, Math.floor(n)) で安全に正規化する
  • 旧来の Array(n + 1).join(str) より 意図が明確で間違えにくい
  • 「N 回繰り返す」→ repeat(N)、「N 文字に揃える」→ padStart/padEnd を使い分ける
  • プログレスバー・区切り線・星評価・マスク・インデントなど実務での活用場面は多い

文字列の置換・検索については【JavaScript】replace()で文字列を置換する方法、数値のゼロ埋めと文字列変換は【JavaScript】数値を文字列として結合する方法もあわせてご覧ください。