JavaScript | ES6+ 文法:変数・宣言の進化 – 再宣言エラー

JavaScript
スポンサーリンク

再宣言エラーとは何か

再宣言エラーは「同じスコープ内で、同じ名前の変数をもう一度宣言しようとすると起きるエラー」です。ES6 以降、let と const は“同スコープの再宣言”を禁止します。ここが重要です:重複宣言をコンパイル時(実行前)に止めることで、意図しない上書きやバグの温床を根絶します。

let a = 1;
// let a = 2; // SyntaxError: Identifier 'a' has already been declared

const b = 3;
// const b = 4; // SyntaxError: Identifier 'b' has already been declared
JavaScript

var は同スコープで再宣言してもエラーにならず、静かに上書きされます(これが事故の元)。

var x = 1;
var x = 2; // OK(上書きされる)— これが意図しない挙動を生みやすい
JavaScript

どこで再宣言エラーが起きるか

同じブロック内で同名の let/const

ブロックスコープ({ } の中)で同じ名前をもう一度宣言するとエラーになります。ここが重要です:スコープの粒度を意識して、必要ならブロックを分ける/名前を変える。

{
  const n = 1;
  // const n = 2; // SyntaxError(同ブロック内の再宣言)
}
JavaScript

switch 文(全体が単一ブロック)

switch 全体が1つのブロックとして扱われます。case ごとに同名の let/const を宣言すると再宣言エラー。対策は、case を { } で囲んで“ブロックを分ける”。

switch (kind) {
  case "A": {
    const msg = "alpha";
    break;
  }
  case "B": {
    const msg = "beta"; // 別ブロックなので OK
    break;
  }
}
JavaScript

import された識別子(再宣言不可)

ES Modules の import で得た名前は“読み取り専用の束縛”で、同スコープで再宣言・再代入できません。

// import { foo } from "./lib.js";
// let foo = 1; // SyntaxError(import名の再宣言は不可)
JavaScript

関数パラメータと同名の let/const

関数の引数名と同じ名前で、関数本体の同スコープに let/const を宣言すると再宣言エラー。

function f(id) {
  // const id = 2; // SyntaxError(引数と同名の再宣言)
}
JavaScript

シャドーイングとの違い(隠すのはOK、同スコープの再宣言はNG)

“別スコープ”なら同名宣言は可能で、外側の変数を内側が“隠す(シャドーイング)”だけです。ここが重要です:シャドーイングは安全に使えるが、紛らわしい名前は避ける。

let label = "outer";
{
  let label = "inner"; // 別ブロックなのでOK(外側を隠す)
  console.log(label);  // "inner"
}
console.log(label);    // "outer"
JavaScript

同じブロックでの重複宣言はNG。違いは“スコープが異なるかどうか”。


TDZ と再宣言の関係(宣言順で守られる安全)

TDZ(Temporal Dead Zone)は“宣言位置までその変数が存在しない扱い”のゾーンです。再宣言エラーとは別ですが、同名を内側ブロックで宣言する予定があると、その宣言より前に同名へ触れると ReferenceError(TDZ)が出ます。ここが重要です:宣言は使う直前に行い、宣言前参照を避ける。

let n = 5;
{
  // console.log(n); // ReferenceError(このブロックで n を宣言予定 → TDZ)
  let n = 10;
}
JavaScript

よくある落とし穴と対策

“var なら動くのに let/const だと落ちる”問題

var は再宣言OK/巻き上げで undefined 化するため“動いてしまう”。let/const はその曖昧さを禁止していると理解しましょう。対策は“同名を作らない、スコープを分ける、宣言を整理する”。

// 悪い例(意図が曖昧)
var name = "A";
var name = "B"; // 上書き

// 良い例(明確な意図)
let name = "A";
name = "B";     // 値を更新するなら“再代入”だけ行う(再宣言しない)
JavaScript

ヘッダと本体の重複(関数パラメータ・for 文)

引数名や for ヘッダで使った識別子と同名の let/const を本体で宣言しない。必要なら別名にする。

for (let i = 0; i < 3; i++) {
  // const i = 99; // SyntaxError(同スコープ再宣言)
}
JavaScript

モジュールのトップレベル衝突

同一ファイルのトップレベルは同スコープ。重複しやすいユーティリティ名は具体化し、1ファイル1束縛にする。

const parseDate = s => new Date(s);
// const parseDate = () => {}; // SyntaxError(同一スコープ再宣言)
JavaScript

実務での指針(安全に書くための癖)

まず const、必要なら let(再宣言ではなく再代入)

不変が基本。値を変える必要があるときだけ let を使い、同スコープで“宣言を増やさない”。

const rate = 0.1;
let total = 0;
for (const it of items) {
  const price = it.price ?? 0;
  total += Math.round(price * (1 + rate));
}
JavaScript

スコープを分けて意図を隔離

switch の case、短い補助計算などは { } でミニブロックを作り、同名を安全に使い分ける。

{
  const key = query.trim().toLowerCase();
  rows = rows.filter(r => r.name.toLowerCase().includes(key));
} // key はこのブロック限り
JavaScript

名前を具体化して衝突を防ぐ

抽象的な名前(data, result, value)は衝突しがち。文脈に合わせた具体名(userList, totalPrice, nextState)にする。


例題で理解を固める

switch の再宣言対策

function handle(kind) {
  switch (kind) {
    case "sum": {
      const op = "add";
      return op;
    }
    case "sub": {
      const op = "minus"; // 別ブロックで安全
      return op;
    }
  }
}
JavaScript

再宣言せずに更新する(正しいパターン)

let count = 0;         // 1回だけ宣言
count += 1;            // 更新は再代入で
count += 1;
JavaScript

まとめ

再宣言エラーの核心は「同スコープで同名の let/const をもう一度宣言すると SyntaxError」であること。var は許すが曖昧で危険、let/const は厳格で安全。ここが重要です:宣言は1回、更新は再代入で行う/スコープを分けて同名の安全なシャドーイングにする/switch は case をブロック化。この癖を身につければ、初心者でも衝突を避け、意図が透けるES6+のコードを書けます。

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