【テクニカル・上級編】【初心者向け】var・let・constの使い分け:なぜ現代のJSではvarを捨ててconstを優先すべきなのか – JavaScriptコア文法・モダン言語仕様と変数・関数解析バイブル

【ランタイム防壁の極意】varを捨て、constを制する者がV8とサプライチェーンの覇権を握る

JavaScriptの歴史を紐解く時、私たちは常に「負債の清算」という文脈に向き合わされてきた。1995年にBrendan Eichがわずか10日間で書き上げた言語は、Webの爆発的な成長と共に巨大なランタイムへと変貌を遂げた。その初期の設計ミス、あるいは仕様の歪みの象徴が `var` である。

初心者向けの記事では、「`var` は古いから `let` や `const` を使おう」といった表層的な説明に終始することが多い。しかし、シニアエンジニアやセキュリティ・アーキテクトを目指す者であれば、コードエディタの表面的な記法が、V8エンジンのJITコンパイラやヒープメモリ、さらにはNode.jsのサプライチェーンセキュリティにどのような物理的影響を及ぼしているのかを理解していなければならない。

本稿では、`var`、`let`、`const` の使い分けという一見初歩的なテーマを起点に、ランタイムの最深部とセキュリティの防壁に至るまでの全貌を解き明かす。

—

1. V8の視座:なぜ `var` はメモリ空間の癌なのか

変数の宣言において、なぜ現代のJavaScriptでは `var` を完全に排除し、可能な限り `const` を優先すべきなのか。その答えは、V8エンジンがメモリ上でオブジェクトと変数をどのように扱い、JIT(Just-In-Time)コンパイル時にどのような最適化を行っているかにある。

スコープの歪みとホイスティング(巻き上げ)の罠

`var` は関数スコープを持つ。これは、ブロック(`{}`)の概念を無視し、最も近い関数スコープ、あるいはグローバルオブジェクト(ブラウザであれば `window`、Node.jsであれば `global`)にまで汚染を広げる。

さらに厄介なのが、コードの実行前に発生する「ホイスティング」だ。

// 【危険なコード】varによる意図しないグローバル汚染と巻き上げ
function evaluateRisk() {
console.log(status); // エラーにならない! “undefined” が出力される

if (true) {
var status = “CRITICAL”;
}

console.log(status); // “CRITICAL”
}

evaluateRisk();
// グローバル空間(または関数スコープのトップ)に `status` が未初期化のままバインドされる

V8エンジンは、コードをパースする際、AST(抽象構文木)を生成し、変数のスコープを静的に解析する。`var` で宣言された変数は、スコープの先頭に「巻き上げ」られ、初期値として `undefined` が代入された状態でメモリ(Variable Object / Environment Record)にスロットを確保される。

これは、ランタイムにとって予測不可能な状態の温床であり、JITコンパイラ(IgnitionからTurboFanへの最適化パイプライン)が「型の予測(Type Feedback)」を行う際の障壁となる。変数の型や存在が動的に揺らぐコードは、TurboFanによるインラインキャッシュ(Inline Caching)の最適化を阻害し、メガモフィック(Polymorphic/Megamorphic)な状態に陥ることで、実行時パフォーマンスを著しく低下させる。

`let` と `const` がもたらす「TDZ(Temporal Dead Zone)」

一方、`let` と `const` はブロックスコープを持ち、TDZ(一時的死空間)という厳格な防壁を導入した。

// 【堅牢なコード】TDZによるランタイムの早期エラー検知
function secureExecution() {
// console.log(secureToken); // ReferenceError: Cannot access ‘secureToken’ before initialization

const secureToken = “v8-opt-hash-xyz”;

if (true) {
// 同じ識別子であっても、ブロックスコープが異なるため衝突しない
const secureToken = “inner-scope-token”;
console.log(secureToken); // “inner-scope-token”
}

console.log(secureToken); // “v8-opt-hash-xyz”
}

secureExecution();

V8は、`let` や `const` の宣言に出会うまでの間、その変数をTDZに置く。この空間で変数にアクセスしようとすると、ランタイムは即座に `ReferenceError` をスローする。これにより、「意図せぬ初期化前のデータ参照」というバグの芽を、コンパイル・実行の極めて初期段階で完全に摘み取ることができるのだ。

—

2. 物理最適化:`const` がV8のヒープと隠しクラス(Hidden Class)に与える恩恵

チーフアーキテクトとして特筆しておきたいのは、`const` を使用することが、単なる「ヒューマンエラーの防止」にとどまらず、V8エンジンのメモリレイアウトとオブジェクトの物理最適化に直結しているという点だ。

イミュータビリティとインライン展開

`const` は「値の変更が不可能(イミュータブル)」を意味するのではなく、「変数のバインディング(参照先のアドレス)の変更が不可能」であることを保証する。

// constで宣言されたオブジェクトのプロパティは変更可能(ミュータブル)
const serverConfig = {
port: 3000,
timeout: 5000
};

serverConfig.port = 8080; // これは合法
// serverConfig = {}; // SyntaxError / TypeError: Assignment to constant variable.

しかし、この「参照の固定」がV8に強力なヒントを与える。コンパイラは、`const` で宣言された変数(特にプリミティブ型や、モジュールスコープレベルで定数化されたオブジェクト)に対して、定数畳み込み(Constant Folding)やインライン展開(Inlining)といったアグレッシブな最適化を適用しやすくなる。

隠しクラス(Hidden Class / Shapes)の安定化

V8は、動的言語であるJavaScriptにおいてC++並みのプロパティアクセス速度を実現するため、「隠しクラス(Mapとも呼ばれる)」という概念を使用する。オブジェクトが持つプロパティの構造(形状)が変わるたびに、V8は新しい隠しクラスを生成する。

もし変数(特にオブジェクトへの参照)が `let` や `var` によって不必要に再代入され、異なる構造のオブジェクトを指し示すようになると、V8のインラインキャッシュはヒットせず、メモリ上のプロパティルックアップは遅いディクショナリモード(ハッシュテーブル検索)へとフォールバックしてしまう。

`const` を徹底し、変数バインディングの不変性を担保することは、コードの予測可能性を高め、V8が安定した隠しクラスを維持し続けるための最大のエンジニアリング上の布石となる。

—

3. セキュリティの深層:なぜ `var` はプロトタイプ汚染(Prototype Pollution)を加速させるのか

ここからが本稿の核心である。フロントエンドからNode.jsのバックエンドに至るまで、現代のWebアプリケーションが直面する最大級の脅威の一つが プロトタイプ汚染(Prototype Pollution) である。そして、この脆弱性を悪用したサプライチェーン攻撃からリモートコード実行(RCE)に至るパスにおいて、`var` や不適切なスコープ管理がどのように加担しているかを暴く。

プロトタイプ汚染のメカニズム

プロトタイプ汚染とは、アプリケーションが不正な入力(JSONの深部マージや再帰的なオブジェクト代入など)を処理する際、グローバルな `Object.prototype` に任意のプロパティを挿入・上書きしてしまう脆弱性である。

// 【脆弱なコード】動的かつ不安全なオブジェクトの再帰的マージ
function unsafeMerge(target, source) {
for (let key in source) {
if (typeof source[key] === ‘object’ && source[key] !== null) {
if (!target[key]) target[key] = {};
unsafeMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}

// 攻撃者が悪意あるペイロードを送り込む
const maliciousPayload = JSON.parse(‘{“__proto__”: {“rceEnabled”: true, “command”: “rm -rf /”}}’);

const globalConfig = {};
unsafeMerge(globalConfig, maliciousPayload);

// Object.prototypeが汚染される!
console.log({}.rceEnabled); // true !!!

なぜ `var` との組み合わせが致命的なのか?

古いコードベースや、安全性に対する意識の低いサードパーティ製ライブラリ(NPMパッケージなど)の多くは、未だに `var` や不適切なクロージャーを用いている。

1. グローバル空間への意図せぬバインド:
`var` を用いて定義された変数が意図せずグローバルオブジェクト(Node.jsでは `global`、ブラウザでは `window`)のプロパティとして露出している場合、プロトタイプ汚染の影響を受けたオブジェクトが、そのグローバル変数の参照解決プロセスを歪める。
2. イベントループと非同期処理における状態のリーク:
Node.jsのイベントループ(マクロタスク:`setTimeout`, I/O と マイクロタスク:`Promise` のキュー処理)において、`var` の関数スコープに起因する変数共有(レキシカル環境の意図せぬ共有)が起きると、汚染されたプロトタイプや改ざんされた設定値が、後続のリクエストや非同期タスクへ伝播する。

サプライチェーン攻撃からRCE(リモートコード実行)へ

Node.js環境において `Object.prototype` が汚染されると、フレームワーク(Express等)やテンプレートエンジン(Pug/EJS等)の内部処理、あるいはデータベースクエリビルダーが「設定値」や「オプション」を参照する際に、汚染されたプロパティを拾ってしまう。

例えば、あるライブラリが内部で `options.execPath` や `options.shell` を安全なデフォルト値として扱おうとした際、プロトタイプ汚染によってそれが `sh` や悪意あるバイナリへのパスに書き換えられていた場合、`child_process.exec()` などのシステムコールを介してリモートコード実行(RCE)へと直結する。

// 【危険な結末】プロトタイプ汚染が引き起こすRCEのイメージ
const { exec } = require(‘child_process’);

function runUserTask(taskConfig) {
// もしタスク設定オブジェクトが Object.prototype.command を継承してしまっていたら…
const cmd = taskConfig.command || “echo ‘Safe Task'”;

// 攻撃者によって書き換えられたコマンドが実行されるリスク
// exec(cmd, (err, stdout) => { … });
}

この連鎖を防ぐための防壁の第一歩が、「不変性の強制(`const`)」であり、「グローバル汚染の根絶(`var` の全廃)」なのである。

—

4. チーフアーキテクトからの提言:モダンJSの防壁を構築せよ

実務の現場において、私たちはどのようなルールを敷くべきか。曖昧な「コーディング規約」ではなく、ランタイムとセキュリティの理にかなった鉄則を提示する。

1. `var` の使用を静的解析(ESLint)で完全犯罪化する
`eslint:recommended` や `@typescript-eslint` を導入し、`no-var` ルールを `error` に設定することはもはや議論の余地がない。コミットフック(Husky等)やCI/CDパイプラインにおいて、`var` を含むコードのビルドを物理的に拒絶せよ。
2. デフォルトは常に `const`、再代入が必要な場合のみ `let`
「とりあえず `let` で宣言しておく」という惰性を断ち切れ。変数は原則すべて `const` で宣言し、カウンタ変数やアルゴリズム上の状態保持など、真に再代入が必要な場合のみ `let` に格上げせよ。これにより、V8エンジンのJIT最適化に対する最大のヒントをコードベース全体で提供し続けることができる。
3. オブジェクトの凍結と入力値のサニタイジング
外部から受け取るJSONや設定オブジェクトは、`Object.freeze()` や `Object.seal()` を用いて構造を固定化し、プロトタイプ汚染のベクトル(`__proto__`, `constructor`, `prototype`)を再帰的にフィルタリング(あるいは `Object.create(null)` によるプロ토タイプを持たないハッシュマップの利用)を徹底せよ。

JavaScriptは、もはや「おもちゃのスクリプト言語」ではない。V8という極限まで最適化された仮想マシン上で動き、クラウドインフラの中枢を支えるシステム言語である。そのランタイムの挙動を支配し、堅牢なセキュリティ防壁を築くのは、ほかならぬ我々エンジニアのコードの端々における「選択」なのだ。

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