with
非推奨;: この機能は非推奨になりました。まだ対応しているブラウザーがあるかもしれませんが、すでに関連するウェブ標準から削除されているか、削除の手続き中であるか、互換性のためだけに残されている可能性があります。使用を避け、できれば既存のコードは更新してください。このページの下部にある互換性一覧表を見て判断してください。この機能は突然動作しなくなる可能性があることに注意してください。
メモ: with
文の使用は推奨されません。混乱を招くバグや互換性問題の原因となる可能性があり、最適化ができなくなり、厳格モードでは禁止されているからです。推奨される代替案は、プロパティをアクセスしたいオブジェクトを一時変数に割り当てることです。
with
文は、文に対するスコープチェーンを拡張します。
構文
with (expression)
statement
expression
-
文を評価するときに使われるスコープチェーンに、与えられたオブジェクトを追加します。オブジェクトの周りの括弧は必須です。
statement
-
任意の文。複数の文を実行するためには、それらの文をグループ化するためにブロック文(
{ ... }
)を使ってください。
解説
識別子には、修飾識別子と非修飾識別子の 2 種類があります。非修飾識別子は、どこから来たのかを示さないものです。
foo; // 非修飾識別子
foo.bar; // bar は修飾識別子
通常、非修飾識別子は、その名前を持つ変数をスコープチェーンで検索することで解決し、修飾識別子は、その名前を持つプロパティをオブジェクトのプロトタイプチェーンで検索することで解決します。
const foo = { bar: 1 };
console.log(foo.bar);
// foo は変数としてスコープチェーン内で見つかる。
// bar は foo の中でプロパティとして見つかる。
例外として、グローバルオブジェクトは、スコープチェーンの最上位に位置し、そのプロパティは自動的にグローバル変数になり、修飾子なしで参照することができます。
console.log(globalThis.Math === Math); // true
with
文は、その文本体の評価の間、このスコープチェーンの先頭に、与えられたオブジェクトを追加します。すべての非修飾名は、上位スコープチェーンで検索される前に、まずオブジェクト内で(in
チェックを介して)検索されます。
非修飾参照がオブジェクトのメソッドを参照した場合、そのメソッドはそのオブジェクトを this
値として呼び出されることに注意してください。
with ([1, 2, 3]) {
console.log(toString()); // 1,2,3
}
オブジェクトは @@unscopables
プロパティを持つことができますが、これはスコープチェーンに(後方互換性のために)追加してはならないプロパティのリストを定義するものです。詳細は Symbol.unscopables
のドキュメントを参照してください。
with
文を使用する理由は、一時変数を 1 つ節約するためと、長いオブジェクト参照を繰り返すことを避けるためにファイルサイズを縮小するためです。しかし、with
文が好ましくない理由はもっとたくさんあります。
-
パフォーマンス:
with
文は、すべての名前検索において、指定したオブジェクトを最初に検索することを強制します。したがって、指定したオブジェクトのメンバーでない識別子はすべて、with
ブロックの中で見つかるのがより遅くなります。さらに、オプティマイザーはそれぞれの非修飾識別子が何を参照しているのかについて仮定することができないため、識別子を使用するたびに同じプロパティの検索を繰り返さなければなりません。 -
可読性:
with
文により、人間の読み手や JavaScript コンパイラーにとって、修飾されていない名前がスコープチェーンで見つかるかどうか、見つかるとしたらどのオブジェクトで見つけられるかを判断するのを難しくなります。以下の例を見てください。jsfunction f(x, o) { with (o) { console.log(x); } }
f
の定義だけを見ていると、with
本体のx
が何を参照しているのかがわかりません。f
が呼び出されて初めて、x
がo.x
であるか、f
の最初の形式引数であるかを判断することができます。もし、2 つ目の引数として渡すオブジェクトでx
を定義し忘れても、エラーは発生しませんが、その代わりに予期せぬ結果が発生します。また、このようなコードが実際にどのような意図を持っているのかも不明です。 -
前方互換性:
with
を使用したコードは、前方互換性がない可能性があります。特に、将来的に複数のプロパティを持つようになるかもしれない、プレーンオブジェクト以外で使用する場合です。次のような例を考えてみましょう。jsfunction f(foo, values) { with (foo) { console.log(values); } }
ECMAScript 5 環境で
f([1, 2, 3], obj)
を呼び出すと、with
文の中にあるvalues
の参照先はobj
に解決されます。ところが、ECMAScript 2015 ではvalues
プロパティがArray.prototype
に導入されました (よって、すべての配列で使用できます)。従って、環境を更新すると、with
文の内部にあるvalues
の参照先は[1, 2, 3].values
に解決されるようになり、バグを引き起こす可能性があります。この具体的な例では、
values
はArray.prototype[@@unscopables]
によってスコープ不可と定義されているので、やはりvalues
引数に正しく解決さ れます。もし、スコープ不可と定義されていなければ、デバッグが困難な課題になるのは目に見えています。
例
with の使用
プロパティを現在のスコープに分割代入して with 文を避ける
通常、プロパティの分割代入によって with
を使用するのを避けることができます。ここでは、with
が余分なスコープを作る動作を模倣するために余分なブロックを作成していますが、実際の使用では、通常はこのブロックを除外することができます。
let a, x, y;
const r = 10;
{
const { PI, cos, sin } = Math;
a = PI * r * r;
x = r * cos(PI);
y = r * sin(PI / 2);
}
IIFE を使用して with 文を避ける
長い名前の参照を何度も再利用しなければならない式を作成する場合、式の中でその長い名前を排除することが目的であれば、式を IIFE で囲み、長い名前を引数として提供することができます。
const objectHavingAnEspeciallyLengthyName = { foo: true, bar: false };
if (((o) => o.foo && !o.bar)(objectHavingAnEspeciallyLengthyName)) {
// This branch runs.
}
with 文とプロキシーを使用して動的な名前空間を生成
with
はすべての変数の探索をプロパティの探索に変換し、Proxy ではすべてのプロパティの探索の呼び出しをトラップすることができます。これらを組み合わせることで、動的な名前空間を作成することができます。
const namespace = new Proxy(
{},
{
has(target, key) {
// `console` のようなグローバルプロパティはトラップしない
if (key in globalThis) {
return false;
}
// すべてのプロパティの参照をトラップ
return true;
},
get(target, key) {
return key;
},
},
);
with (namespace) {
console.log(a, b, c); // "a" "b" "c"
}
仕様書
Specification |
---|
ECMAScript Language Specification # sec-with-statement |
ブラウザーの互換性
BCD tables only load in the browser