変数のシャドーイングとは何か
変数のシャドーイングとは、外側のスコープにある変数と同じ名前の変数が内側のスコープで宣言されたとき、内側の変数が外側の変数を「隠す」現象のことです。コードを実行すると、名前が同じでも「今いるスコープの変数」が優先され、外側の同名変数は見えなくなります。これはレキシカルスコープ(定義位置で見える範囲が決まる仕組み)の自然な挙動です。
ここが重要です:
- 「内側が優先される」というルールにより、意図せず外側の値が使われないことがあります。変数名のつけ方とスコープの意識が、バグを避ける鍵になります。
なぜシャドーイングが起こるのか(深掘り)
JavaScript は変数を探すとき、今いるスコープから外側へ段階的に探索します(スコープチェーン)。同じ名前が内側にあれば、外側の探索に進む前に見つかった内側の変数で確定します。つまり「近いほうが勝つ」。このため、外側の変数は存在していても、内側で同名を宣言すると内側が使われます。
- 実務では「同じ名前を別スコープで使う」こと自体は普通ですが、意味が紛れると可読性が落ちます。意図をはっきりさせるために、内外で役割の異なる変数に同じ名前を付けない工夫が重要です。
基本例で理解する
例題1:内側が外側を隠す
const name = "太郎"; // 外側
function greet() {
const name = "花子"; // 内側(シャドーイング)
console.log(`こんにちは、${name}`); // 花子
}
greet();
console.log(name); // 太郎(外側はそのまま)
JavaScript関数内では「近いほう」の name が優先され、外側の name は見えません。
例題2:ブロックスコープでのシャドーイング
let total = 100; // 外側
if (true) {
let total = 5; // ブロック内でシャドーイング
console.log(total); // 5
}
console.log(total); // 100
JavaScriptlet/const はブロックスコープを持つため、{} 内の同名が外側を隠します。
例題3:関数引数もシャドーイングする
const rate = 0.1;
function calc(price, rate) { // 引数 rate が外側の rate を隠す
return Math.floor(price * (1 + rate));
}
console.log(calc(1000, 0.08)); // 1080
JavaScript引数名が外側の変数名と同じだと、引数が優先されます。引数は「関数の最も近いスコープ」にあるためです。
var と let/const の違いが生む影響(注意点を深掘り)
var は関数スコープ、let/const はブロックスコープです。どこに「内側のスコープ」が生まれるかが違うため、シャドーイングの起こり方にも差が出ます。
var x = 1;
if (true) {
var x = 2; // ブロックでは新しいスコープができない(上書き)
}
console.log(x); // 2(隠すのではなく同じスコープで再宣言)
let y = 1;
if (true) {
let y = 2; // ブロックスコープでシャドーイング
console.log(y); // 2(内側が優先)
}
console.log(y); // 1(外側は保たれる)
JavaScriptここが重要です:
varは「隠す」ではなく「同じスコープに再宣言されて上書き」になりがちで、予期せぬ影響を広げます。シャドーイングのコントロール性を高めるためにも、基本はlet/constを使いましょう。
シャドーイングによる落とし穴(深掘り)
誤った上書きと読み違い
内側の変数が外側を隠しているのに、外側が使われる前提でコードを書くと誤動作します。読み手も「どの値が参照されるか」を取り違えやすいので、名前を変えるかスコープを分けることで意図を明確にするべきです。
let config = { mode: "prod" };
function setup() {
let config = { mode: "dev" }; // シャドーイング
// ここでの config は dev、外の config とは別物
}
JavaScriptデバッグの難しさ
ログに同じ名前を出しても、スコープで指している実体が違うため、原因追跡が難しくなります。デバッグ時は「どのスコープの変数か」を必ず確認しましょう。
シャドーイングをうまく使うコツ
- 内側で短命の補助変数を使うときだけ、外側と同名を許容する。処理のまとまりが明確なら読みやすさが保てます。
- 役割が異なるなら名前を変える(例:外側
user, 内側currentUser)。 let/constを使い、ブロックスコープで安全に閉じ込める。varは上書きの危険があるため避ける。- 関数引数名は外側の重要な変数名と衝突させない。引数が優先されることを常に意識する。
まとめ
変数のシャドーイングは、内側スコープの同名変数が外側を「隠す」ことで起こります。レキシカルスコープの探索順(内→外)により、近いほうが優先されます。let/const では安全にブロックスコープで隠せますが、var は同じスコープで再宣言されやすく、思わぬ上書きを招きます。名前の設計とスコープの意識を徹底すれば、読みやすく安全なコードを保てます。
