JavaScriptにおける変数とスコープの深掘り

JavaScriptの変数挙動は、他の言語とは異なる特有の振る舞いを示します。特に、宣言のタイミングと実行時の可視性(スコープ)に関する理解が、予期せぬバグを回避する鍵となります。

ホイスト現象:宣言の自動持ち上げ

JavaScriptエンジンは、関数内のvar宣言を実行前に「持ち上げ(hoist)」ます。これは、初期化(代入)ではなく、単なる宣言が先に処理されることを意味します。

'use strict';

function calculateGreeting() {
  const result = `Hi, ${userName}!`;
  console.log(result);
  var userName = 'Alex';
}
calculateGreeting(); // "Hi, undefined!"

このコードでは、userNameconsole.log実行時点で宣言済みですが、まだ値が代入されていないためundefinedが補完されます。エンジンが内部で解釈する形は以下の通りです:

function calculateGreeting() {
  var userName; // 宣言のみが持ち上げられる
  const result = `Hi, ${userName}!`;
  console.log(result);
  userName = 'Alex'; // 代入はここに残る
}

したがって、関数内ではすべてのvar変数を先頭で明示的に宣言することが推奨されます。

グローバルコンテキストとwindowオブジェクト

関数外で宣言された変数は、グローバルスコープに属し、ブラウザ環境ではwindowオブジェクトのプロパティとして暗黙に登録されます。

'use strict';

const appTitle = 'JS Mastery';
console.log(appTitle);        // "JS Mastery"
console.log(window.appTitle); // "JS Mastery"

同様に、関数式で定義されたトップレベルの関数も、windowに紐づけられます:

'use strict';

const launchApp = function() {
  return 'Initialized';
};

launchApp();      // 正常実行
window.launchApp(); // 同様に実行可能

この仕様により、名前空間の衝突リスクが高まります。これを防ぐため、アプリケーション全体を1つのルートオブジェクトに閉じ込める手法が広く採用されています:

// 単一のグローバル識別子:APP_CORE
const APP_CORE = {
  version: '2.4.0',
  config: { debug: true },
  utils: {
    formatDate: (date) => date.toISOString().split('T')[0]
  }
};

ブロックスコープの導入:letとconst

varは関数スコープしか持たないため、forループ内で宣言した変数はループ外でも参照可能です。ES6以降では、letconstがブロックスコープを提供します。

'use strict';

function processItems() {
  let total = 0;
  for (let idx = 0; idx < 5; idx++) {
    total += idx;
  }
  console.log(total); // 10
  // console.log(idx); // ReferenceError: idx is not defined
}

constは再代入不可な定数を宣言し、再代入試行は静的エラーまたは無視されます(厳格モード下ではTypeError):

'use strict';

const MAX_RETRY = 3;
// MAX_RETRY = 5; // TypeError(一部のランタイムでは無視)

構造的デストラクチャリング:効率的な値抽出

ES6のデストラクチャリングは、配列やオブジェクトから値を簡潔かつ意図的に抽出するための構文です。

配列からの抽出例:

'use strict';

const [first, second, third] = ['Alpha', 'Beta', 'Gamma'];
console.log(first);  // "Alpha"
console.log(second); // "Beta"

// ネストした配列も可能
const [a, [b, c]] = ['X', ['Y', 'Z']];
// a === 'X', b === 'Y', c === 'Z'

// スキップもサポート
const [, , last] = ['one', 'two', 'three']; // last === 'three'

オブジェクトからの抽出では、プロパティ名と変数名を一致させるか、別名を指定できます:

'use strict';

const user = {
  id: 101,
  profile: {
    name: 'Taylor',
    role: 'admin'
  }
};

const { id, profile: { name: fullName } } = user;
console.log(id);       // 101
console.log(fullName); // "Taylor"

// デフォルト値付き
const { status = 'active', permissions = [] } = user;
console.log(status); // "active"(userにstatusプロパティがない場合)

既存の変数への再代入には括弧が必要です(ブロックと誤認されないため):

let u, v;
({ u, v } = { u: 42, v: 99 }); // 正しい

実践的な応用例

デストラクチャリングは、コードの可読性と保守性を高めます:

  • 2変数の値交換:[a, b] = [b, a]
  • URL解析:const { protocol, host, pathname } = window.location;
  • オブジェクト引数の関数:function createPost({ title, content, tags = [] }) { ... }

タグ: javascript ES6 hoisting scope destructuring

6月7日 19:21 投稿