JavaScript | 基礎構文:スコープ・実行コンテキスト - 変数のシャドーイング

JavaScript JavaScript
スポンサーリンク

変数のシャドーイングって何者?

変数のシャドーイングは、
「内側のスコープで同じ名前の変数を宣言して、外側の変数を“影に隠してしまう”こと」 です。

“shadow(影)”という名前の通り、
外側にある同じ名前の変数が、内側の変数によって見えにくくなります。

シャドーイング自体は「仕様どおりの普通の挙動」ですが、
意識せずにやると「どの値を見ているのか分からない」状態になりやすいので、
仕組みをちゃんと理解しておくことが大事です。


一番シンプルなシャドーイングの例

外側と内側で同じ名前を使った場合

let value = "グローバル";

function show() {
  let value = "ローカル";
  console.log(value);
}

show();
console.log(value);
JavaScript

実行結果はこうなります。

ローカル
グローバル

関数 show の中では、
let value = "ローカル"; が宣言されています。

このとき、show の中から value と書くと、
外側の「グローバルの value」ではなく、
内側の「ローカルの value」が使われます。

外側の value は消えたわけではなく、
「内側の value によって“隠されている(shadow されている)」 状態です。


スコープのルールとシャドーイング

「一番近いスコープの同名変数」が優先される

JavaScript のスコープ探索は、
「内側から外側へ向かって」行われます。

  1. まず“今いるスコープ”にその名前があるか探す
  2. なければ一つ外側のスコープを見る
  3. それでもなければ、さらに外側へ…
  4. 最後までなければエラー

という流れです。

シャドーイングが起きているときは、
「内側のスコープに同じ名前の変数がある」ので、
そこで探索が止まり、外側の変数は参照されません。

let x = 1;

function outer() {
  let x = 2;

  function inner() {
    let x = 3;
    console.log(x);
  }

  inner();
}

outer();
JavaScript

この場合、出力は 3 です。

inner の中で x を探すとき、
まず inner のスコープに x がある → それを使う
ので、外側の x(2 や 1)は見に行きません。

ここが重要です。
「同じ名前の変数が複数あっても、“一番内側のもの”が優先される。
それが外側を“影に隠す”=シャドーイング。」


ブロックスコープでもシャドーイングは起きる

if や for の中で同じ名前を使った場合

let message = "外側";

if (true) {
  let message = "内側";
  console.log("ブロック内:", message);
}

console.log("ブロック外:", message);
JavaScript

結果はこうです。

ブロック内: 内側
ブロック外: 外側

if ブロックの中で let message = "内側"; を宣言したことで、
そのブロック内では「内側の message」が優先されます。

外側の message はそのまま残っていて、
ブロックの外からは「外側の message」が見えます。

これもシャドーイングです。
関数スコープだけでなく、ブロックスコープでも同じことが起きます。


シャドーイングが“危険”になるパターン

「同じ名前を使っていることを忘れて混乱する」

例えば、こんなコード。

let total = 0;

function add(numbers) {
  let total = 0;
  for (const n of numbers) {
    total += n;
  }
  return total;
}

const result = add([1, 2, 3]);
console.log("関数の結果:", result);
console.log("グローバルの total:", total);
JavaScript

結果はこうです。

関数の結果: 6
グローバルの total: 0

add の中で let total = 0; と書いたことで、
関数内の total は「ローカル変数」になり、
グローバルの total をシャドーイングしています。

もし書いた本人が
「関数の中でグローバルの total を更新しているつもり」
だったとしたら、これはバグになります。

ここが重要です。
シャドーイング自体は悪ではないけれど、
「同じ名前を使っていることを意識していないシャドーイング」は
バグの温床になる。


意図的なシャドーイングと、うっかりシャドーイング

意図的に「外側を隠す」こともある

ときには、
「外側の変数をあえて隠したい」こともあります。

const config = { mode: "dev" };

function run() {
  const config = { mode: "prod" }; // この関数内では prod を使いたい
  console.log("run:", config.mode);
}

run();
console.log("global:", config.mode);
JavaScript

結果はこうです。

run: prod
global: dev

この場合、

グローバルの config はアプリ全体の設定
run の中の config は「この関数専用の上書き設定」

という意図で、
あえて同じ名前を使ってシャドーイングしています。

「このスコープの中では、外側の設定を上書きしたい」
というときに、意図的なシャドーイングは有効です。

うっかりシャドーイングは避けたい

一方で、
「たまたま同じ名前を使ってしまっただけ」のシャドーイングは危険です。

let data = fetchFromServer();

function process() {
  let data = []; // なんとなく data と名付けた
  // ここで外側の data を使っているつもりで書くとバグる
}
JavaScript

こういうときは、
内側の変数名を変える(list, items など)ことで、
シャドーイングを避けた方が安全です。


実務での「シャドーイングとの付き合い方」

基本方針:同じ名前を多用しない

実務では、

「スコープごとに同じ名前を使いまくる」
data, value, result ばかり」

といったコードは、
シャドーイングによる混乱を招きやすいです。

できるだけ、

変数名に役割を込める(userData, totalPrice など)
外側と内側で意味が違うなら、名前も変える

という意識を持つと、
「意図しないシャドーイング」をかなり減らせます。

それでもシャドーイングするなら“自覚的に”

どうしても同じ名前を使いたいときは、

「ここで外側をシャドーイングしている」
と自分で分かるようにしておくこと。

コメントを書いてもいいし、
コードレビューで「ここは意図的です」と説明できる状態にしておくと良いです。

ここが重要です。
シャドーイングは“してはいけないこと”ではなく、
“意識してやるべきこと”。
無自覚にやると危険、理解したうえで使えば武器になる。


初心者として「変数のシャドーイング」で本当に押さえてほしいこと

変数のシャドーイングは、

「内側のスコープで同じ名前の変数を宣言すると、
そのスコープ内では外側の変数が“隠れる”」

という現象です。

スコープ探索は「内側から外側へ」行われるので、
一番近いスコープの同名変数が優先される。

その結果、

外側と内側で同じ名前を使うと、
「どの値を見ているのか」が分かりにくくなることがある。

だからこそ、

「この名前、外側でも使ってないか?」
「意味が違うなら名前も変えられないか?」

と一度立ち止まるクセが大事です。

もし今書いているコードで、
外側と内側に同じ名前の変数がいたら、
それが「意図したシャドーイング」なのか、
「たまたまかぶってしまっただけ」なのか、
一度自分に問いかけてみてください。

その小さな確認が、
スコープを“なんとなく”ではなく“理解して”扱えるプログラマーへの一歩になります。

タイトルとURLをコピーしました