やあ、お疲れ。最近、コードレビューをしていて「おっ、いい着眼点だな」と思う一方で、「いや、そこは素直に標準メソッド使おうぜ!」とツッコミを入えたくなる場面によく遭遇するんだ。
今回は、JavaScriptの文字列操作系メソッドの中でも、地味ながらを知っておくとコードが劇的に美しくなる隠れた実力者、`String.prototype.repeat()` について深掘りしていこうと思う。
中級へのステップアップとして、単に「文字列を繰り返すんでしょ?」という表面的な理解を超えて、ブラウザの裏側の動きや、現場でどういう時に使うとスマートなのか、俺の経験を交えて伝授するよ。
—
1. `String.prototype.repeat()` とは何か?(基本仕様のおさらい)
まずは基本だ。`repeat()` は、呼び出した文字列を指定された回数だけ繰り返した新しい文字列を生成して返すメソッドだ。元の文字列が書き換わるわけじゃない(イミュータブル)。これ基本中の基本ね。
構文はシンプルだ。
str.repeat(count)
- `count`: 文字列を繰り返す回数。`0` 以上で、無限大 (`Infinity`) 未満の整数を指定する。
数秒で試せるコードを書いておこう。
// 単純な文字の繰り返し
const echo = “Yo! “;
console.log(echo.repeat(3)); // 出力: “Yo! Yo! Yo! ”
// 0回を指定すると空文字が返る
console.log(“hello”.repeat(0)); // 出力: “”
引数の挙動でハマりやすい罠
ここ、実務で意外とやらかすポイントなんだけど、`count` に小数を渡したり、負の数を渡したりするとどうなると思う?
1. 小数を渡した場合: 小数点以下は切り捨てられる(例: `2.9` は `2` として扱われる)。
2. 負の数や `-Infinity` を渡した場合: 例外(`RangeError`)がスローされる。
3. `Infinity` を渡した場合: 同様に `RangeError` がスローされる(メモリが枯渇するから当然だよね)。
// 小数は切り捨てられる
console.log(“ab”.repeat(2.5)); // 出力: “abab” (2回繰り返される)
try {
console.log(“ab”.repeat(-1)); // 負の数はRangeError
} catch (e) {
console.error(e.message); // Invalid count value
}
「あれ、なんでここでエラーになるんだっけ?」と迷ったら、大体この引数のバリデーション仕様に引っかかっているケースが多い。覚えておいて損はないよ。
—
2. ブラウザの裏側で何が起きているのか?(V8エンジン等の内部処理)
さて、ここからがシニアらしい少し踏み込んだ話だ。
俺たちが何気なく書いている `repeat()`。ブラウザのJavaScriptエンジン(例えばChromeのV8など)の内部では、裏でどんなメモリ操作が行われているか想像したことはあるか?
文字列結合のアンチパターンとしてよく「ループの中で `+` 演算子や `+=` を使って文字列を繋ぎまくるな」って言われるよね。あれは、結合のたびに新しいメモリ領域の確保とガベージコレクション(GC)の負荷が発生するからだ。
一方、`repeat()` はどうだろう?
仕様上、結果の文字列の長さ(`str.length count`)は事前に正確に計算できる。そのため、V8などのモダンなエンジンは、必要十分なメモリ領域を最初の一回で一気に確保(プリアロケーション)し、そのメモリ空間に対して一気にデータを書き込む最適化が行えるんだ。
要するに、手動で `for` ループを回して文字列を連結していくよりも、内部のC++レベルで最適化されたメモリコピーが走るため、圧倒的に高速で、メモリ効率が良い。パフォーマンスを気にする場面では、自前でループを書くより標準の `repeat()` を信用して使うべき理由はここにある。
—
3. 現場で使える!実践的ユースケース 3選
「理屈はわかったけど、実務のどこで使うんだよ」って?
任せてくれ。フロントエンドの現場で「おっ、この書き方スマートだな」と思われる具体的なユースケースを3つ紹介しよう。
① フォーム入力の「パスワードマスク表示」
例えば、ユーザーが入力したパスワードの一部を伏せ字(“ や `●`)にする処理。最後の4文字以外をマスクする、なんて要件はよくあるよね。
/
- パスワードの末尾4文字以外をマスクする関数
- @param {string} password – 実際のパスワード
- @returns {string} マスクされた文字列
/
function maskPassword(password) {
if (password.length <= 4) return password;
const visiblePart = password.slice(-4);
const maskLength = password.length - 4;
// ● をマスクする長さ分だけ繰り返して、末尾4文字と結合する
return "●".repeat(maskLength) + visiblePart;
}
console.log(maskPassword("supersecret1234"));
// 出力: "──────────1234" (●が11個 + "1234")
`slice` と `repeat` のコンビネーションだ。こういうところでサッと `repeat` が出てくるとコードが洗練されて見える。
② ページネーションやインデントの動的生成
デザインシステムやコンポーネントライブラリを自作していると、ツリー構造の深さに応じてインデント(スペースや記号)を挿入したい場面に直面する。
/
- ツリー構造の深さに応じたインデント文字列を生成する
- @param {number} depth – 階層の深さ (0始まり)
- @param {string} [indentChar=” “] – インデントに使う文字列(デフォルトはスペース2つ)
- @returns {string} インデント用文字列
/
function createIndentation(depth, indentChar = ” “) {
return indentChar.repeat(depth);
}
// 階層に応じたプレフィックス
console.log(createIndentation(0) + “Root”); // 出力: “Root”
console.log(createIndentation(1) + “Child”); // 出力: ” Child”
console.log(createIndentation(3) + “GrandChild”); // 出力: “GrandChild”
再帰的なコンポーネントや、ログ出力のフォーマッターなんかで地味に重宝するテクニックだ。
③ ログやデバッグ出力の区切り線(Separator)
開発中、コンソールに視覚的な区切り線を出したいとき、わざわざハードコーディングしていないかい? ブラウザの幅に合わせて動的に変えたいときにも `repeat` は便利だ。
/
- 指定した文字でコンソール用の区切り線を生成する
- @param {string} [char=”-“] – 繰り返す文字
- @param {number} [length=40] – 長さ
- @returns {string} 区切り線
/
function printSeparator(char = “-“, length = 40) {
console.log(char.repeat(length));
}
printSeparator(“=”, 30);
// 出力: ================================== (30文字)
console.log(“【データ取得処理開始】”);
printSeparator(“-“, 30);
// 出力: ———————————- (30文字)
こういうユーティリティ関数をサクッと定義しておくと、チーム全体の開発体験(DX)がちょっとだけ向上する。
—
4. まとめ:標準メソッドを愛そう
今回は `String.prototype.repeat()` について、基本仕様から内部のメモリ効率、そして実務での活用シーンまで解説した。
JavaScriptの世界には、今回のような「知っていれば数行で美しく書けるのに、知らないと無駄な長文ループを書いてしまう」ような便利なAPIがたくさん転がっている。車輪の再発明をする前に、まずはMDNや標準仕様を眺めてみる習慣をつけてほしい。
日々のコードの端々に、こうした「プロの引き出し」を散りばめていけるよう、一緒に腕を磨いていこうぜ。それじゃ、また次のコードレビューで!

コメント