JavaScript・JScript にも with ステートメントってあったんだ…

VBA とか VBScript で時々お世話になる「With 構文」、With ステートメント。JavaScript や JScript においても同様の記法があることをついこの前知った。

分かりやすい使用例

1つの要素に複数のスタイルを指定するようなとき、

document.getElementById("hoge").style.color = "#ff0000";
document.getElementById("hoge").style.width = "200px";
document.getElementById("hoge").style.height = "500px";

こんなコードが続くと鬱陶しい。これを With 文で書くと、こんな風に書ける。

with(document.getElementById("hoge").style) {
  color = "#ff0000";
  width = "200px";
  height = "500px";
}

これは便利な気がする。

ではなぜ今まで見かけなかったのか?

曖昧な記法だから非推奨

端的にいうと、どうやら、非推奨な記法らしいのだ。

MDN では以下のように非推奨とされている。

混乱させるバグや互換性問題の原因になり得るため、with 文の使用は推奨されません。

欠点: with 文 は、非修飾名がスコープチェーンの中で見つかるかどうか、もし見つかるならどのオブジェクトの中でかを、人間の読み手または JavaScript コンパイラが決定するのを難しくします。

少し分かりづらい表現だろうか。

では以下の記事を。

with文の中で該当しないプロパティやメソッドを呼ぶと、プロトタイプをさかのぼって探しに行くから。

例えば、

// 任意の modal オブジェクト
var modal = {
  // 「clo "s" e」を typo している
  cloze: function() { alert("閉じる"); }
};

with(modal) {
  close();
  // modal の関数は「clo "z" e」と typo しているので、modal には close() は存在しない…
}

上のような誤りを含んだコードの場合に、modal には close() がないので、プロトタイプを遡って close() を探しにいき、最終的に window オブジェクトには close() があったのでコレを実行する、と。つまりウィンドウが閉じられてしまうのだ。

さらに言語仕様を追っていくと、 With ステートメントはスコープチェーンに割り込んでいるみたい。

withの仕組み

with構文は、実際に変数を展開しているわけではなく、スコープチェーンに割り込むことでこの挙動を実現しています。通常、変数が実際に何を指しているかは、以下の順番で探索されます。ここでいうCallオブジェクトとは、varで定義された変数が格納されている内部オブジェクトの名称です。

  1. Callオブジェクトのメンバ
  2. 一つ外側のCallオブジェクトのメンバ
  3. (中略)
  4. グローバルオブジェクトのメンバ

with構文はこの探索順序に割り込みをかけて、常にwithで宣言したオブジェクトが最優先で探索されるようにします。

  1. withで宣言されたオブジェクトのメンバ
  2. Callオブジェクトのメンバ
  3. …中略…
  4. グローバルオブジェクトのメンバ

上の例で window.close() 実行されてしまうのは、「1. withで宣言されたオブジェクトのメンバ」に close() がなかったので、最終的に「4. グローバルオブジェクトのメンバ」である window オブジェクトの close() まで遡って実行されてしまった、ということだ。

Strict モードでは禁止

"use strict"; で Strict モードにした場合、With ステートメントはシンタックスエラーになるようだ。

function foo() { "use strict"; with({}); }
SyntaxError: strict mode code may not contain 'with' statements

代替案は

一見便利そうなので、使えるなら使いたいと思った With 構文だが、非推奨らしいので、代替案を探す。

MDNによると、

推奨される代替案は、参照したいプロパティを持つオブジェクトを一時変数に代入することです。

とのこと

確かに以下に記述があった。

これはどういうことかというと、先程のコードでいえば、

var hogeStyle = document.getElementById("hoge").style;

hogeStyle.color = "#ff0000";
hogeStyle.width = "200px";
hogeStyle.height = "500px";

こんな風に書きなさい、ということ。

結局こうなるかぁ。

個人的に、With ステートメントというのは「ある対象のオブジェクトを操作する一連の処理」を、ブロックによるインデントで区別して表現できるので、可読性が向上すると思っているんだけど、JavaScript ではダメですかぁ。残念。

参考