【入門編】eval()とwith文がスコープチェーンを破壊する理由:静的解析が不可能になるコンパイル時の悲劇 – JavaScriptコア文法・モダン言語仕様と変数・関数解析バイブル

こんにちは!JavaScriptの世界へようこそ。
フロントエンドの細かい描画の仕組みから、裏側で動くV8エンジンの挙動までを知り尽くしたチーフアーキテクトの私と一緒に、JavaScriptの奥深い世界を覗いてみましょう。

今回は、JavaScriptの歴史の中でも特に「魔王」のように恐れられている2つの構文、`eval()`関数と`with`文についてお話しします。

「なぜこれらは現代の開発で絶対に使ってはいけないのか?」
「なぜESモジュールや厳格モード(strict mode)で厳しく弾かれるのか?」

その理由は、単に「行儀が悪いから」ではありません。JavaScriptのエンジンが高速にコードを動かすための「静的な最適化(コンパイル時の予測)」の仕組みを根本から破壊してしまうからなのです。

ここをクリアすれば、JavaScriptが裏側でどうやってメモリを管理し、どうやってコードを最適化しているのかの本質がバッチリ見えてきますよ。さあ、一緒に紐解いていきましょう!

—

1. 変数のスコープと「静的スコープ」の基本

まず大前提として、私たちが普段書いているJavaScriptの変数や関数が、どのように探されているのかをおさらいしておきましょう。

JavaScriptは、コードを書いた段階(コンパイル時やパース時)で変数の置き場所が決まる「静的スコープ(レキシカルスコープ)」という仕組みを採用しています。

const globalVar = “私はグローバル”;

function outer() {
const outerVar = “私はアウター”;

function inner() {
// innerから見ると、outerVarもglobalVarも「どこにあるか」がコードを書いた時点で確定している
console.log(outerVar);
}
inner();
}

outer();

V8のような最新のJavaScriptエンジンは、この「どこにどの変数があるかあらかじめ分かっている」という事実をフル活用して、メモリの配置や処理スピードを限界まで最適化(JITコンパイル)しています。「この変数は関数のローカルメモリ(レジスタやスタックに近い領域)のここにあるな」と、エンジンが事前に予測できるわけです。

ところがここに、その予測をすべてブチ壊す「危険な暴れ馬」が登場します。それが `eval()` と `with` 文です。

—

2. eval() の正体:文字列をその場でコードにしてしまう呪文

`eval()` は、引数に渡した文字列を、まるで最初からそこに書いてあったかのようにその場でJavaScriptのコードとして実行してしまう恐ろしい関数です。

基本的な使い方(※良い子は真似しないでください)

const x = 10;
const y = 20;

// 文字列としてコードを渡す
const code = “console.log(x + y);”;
eval(code); // 実行結果: 30

一見すると、「動的にプログラムを作れて便利じゃないか!」と思うかもしれません。しかし、これがV8エンジンの最適化パイプラインにおいて大惨事を引き起こします。

なぜ eval() は V8 を泣かせるのか?

V8エンジンは、コードを読み込んだときに「この関数の中では、変数 `x` と `y` しか使われていないな。だからこのメモリ領域を確保しよう」と計画を立てます。

しかし、コードの中に `eval(“x = 100;”)` のような文字列が混ざっていたらどうでしょう?
エンジンは「この文字列の中に、今まで存在しなかった新しい変数を生み出すコードや、既存の変数をこっそり書き換えるコードが入っているかもしれない……!」と怯えることになります。

結果として、エンジンはコードの安全な最適化を諦めざるを得ません。
「あぁ、もう予測ができないから、この周辺の変数はすべて、いつでもどこからでもアクセスできるようにスピードの遅い動的な仕組み(遅延バインディング)で管理しよう……」と、エンジンのギアを最低速に落としてしまうのです。これがパフォーマンス低下の大きな原因になります。

—

3. with文の正体:スコープを強制的にねじ曲げる異端児

もう一つの魔王が `with` 文です。これは、オブジェクトのプロパティをスコープチェーンの先頭に無理やり割り込ませるための構文です。

基本的な使い方(※こちらも絶対に実務では使わないでください)

const user = {
name: “太郎”,
age: 25,
country: “日本”
};

// with文を使うと、userのプロパティをオブジェクト名なしで直接参照できる
with (user) {
console.log(name); // “太郎” と表示される
console.log(age); // 25 と表示される
console.log(country); // “日本” と表示される
}

一見、タイピング量が減って便利に見えるかもしれませんが、これもJavaScriptの静的解析を完全に破壊します。

with文が引き起こす「名前の衝突」の悲劇

次のコードを見てください。

let message = “こんにちは”;

function showInfo(obj) {
with (obj) {
// ここで使われている message は、外の変数なのか?
// それとも obj.message というプロパティなのか?
console.log(message);
}
}

showInfo({ name: “花子” });

もし引数で渡された `obj` の中にたまたま `message` というプロパティが含まれていたら、外の `message` は隠蔽され、意図しない値が参照されてしまいます。

さらに最悪なのは、コードを書いている人間にも、解析しているV8エンジンにも、実行するまでどちらを指しているのか絶対に分からないということです。

V8は「コードのこの部分は外の変数を見にいっているはずだ」という予測(キャッシュ)が一切できなくなります。その結果、変数を参照するたびに「ええっと、今見ているスコープの中にこの名前はあるか? 親のスコープは? オブジェクトのプロパティは?」と、実行時(ランタイム)に毎回総当たりで探し回ることになり、パフォーマンスが地に落ちてしまいます。

—

4. 現代のJavaScriptにおける結末:静的解析の勝利

こうした背景があるため、現代のJavaScript開発において、これら2つの構文は「悪」とされています。

1. 厳格モード(Strict Mode / `’use strict’;`)では、`with` 文の使用は文法エラー(SyntaxError)として強制的に弾かれます。
2. 現代のモジュールシステム(ES Modules)や、Webpack / Vite などのモダンなバンドラ、そしてTypeScriptなどの静的型付け言語は、すべて「コードが事前に完全に予測できること(静的解析)」を前提に高速化やトランスパイルを行っています。

もしコードのどこかに `eval()` が紛れ込んでいると、静的解析ツールは「この先は何が起きるか分からない」として、コードの圧縮や最適化の効率を大きく落としてしまいます。

—

まとめ:ここをクリアすれば基本はバッチリ!

  • 静的スコープ(レキシカルスコープ):変数の位置がコードを書いた時点で決まるから、JavaScriptは高速に動作できる。
  • `eval()`:文字列をコードに変換するため、エンジンの予測を裏切り、最適化を停止させる。
  • `with`文:スコープを動的に改変するため、変数がどこにあるか実行時まで分からなくなり、バグの温床になりかつ極めて低速になる。

「動的に何かを評価したい、オブジェクトのプロパティをスッキリ扱いたい」と思ったときは、`eval` や `with` に頼るのではなく、適切なオブジェクトの分割代号(Destructuring)や、マッピング用のデータ構造(`Map` や連想配列)を使いましょう。

この「コンパイル時と実行時の違い」「エンジンの気持ち(最適化)」を意識できるようになると、あなたの書くコードのパフォーマンスは劇的に変わります。ここまで理解できれば、JavaScriptの変数とスコープの基本はもうバッチリマスターしていますよ!

自信を持って、次のステップへ進んでいきましょう。

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