with
已弃用:此功能不再推荐。尽管某些浏览器可能仍然支持它,但它可能已从相关的 Web 标准中删除,可能正在被删除的过程中,或者可能仅出于兼容性目的而保留。避免使用它,并尽可能更新现有代码;请参阅此页面底部的兼容性表,以指导您的决策。请注意,此功能可能随时停止工作。
注意:不建议使用with
语句,因为它可能是令人困惑的错误和兼容性问题的原因,使得优化无法进行,并且在严格模式中是被禁止的。推荐的替代方法是将您要访问其属性的对象分配给一个临时变量。
with
语句扩展了语句的范围链。
语法
描述
标识符有两种类型:限定标识符和非限定标识符。非限定标识符是不指示其来源的标识符。
foo; // unqualified identifier
foo.bar; // bar is a qualified identifier
通常,非限定标识符通过在作用域链中搜索具有该名称的变量来解析,而限定标识符通过在对象的原型链中搜索具有该名称的属性来解析。
const foo = { bar: 1 };
console.log(foo.bar);
// foo is found in the scope chain as a variable;
// bar is found in foo as a property
一个例外是全局对象,它位于作用域链的顶部,其属性自动成为全局变量,可以无需限定符即可引用。
console.log(globalThis.Math === Math); // true
with
语句在评估其语句体期间将给定的对象添加到此作用域链的头部。每个非限定名称都将在对象内(通过in
检查)进行搜索,然后再在较高的作用域链中搜索。
请注意,如果非限定引用引用了对象的某个方法,则该方法将以对象作为其this
值来调用。
with ([1, 2, 3]) {
console.log(toString()); // 1,2,3
}
对象可能具有[Symbol.unscopables]
属性,该属性定义了不应添加到作用域链中的属性列表(用于向后兼容)。有关更多信息,请参阅Symbol.unscopables
文档。
使用with
语句的原因包括节省一个临时变量并通过避免重复冗长的对象引用来减小文件大小。但是,有更多理由说明为什么with
语句不可取
- 性能:
with
语句强制首先搜索指定的对象以进行所有名称查找。因此,在with
块中,所有不是指定对象成员的标识符都将更慢地找到。此外,优化器无法对每个非限定标识符引用什么做出任何假设,因此它必须在每次使用标识符时重复相同的属性查找。 - 可读性:
with
语句使人类读者或 JavaScript 编译器难以确定非限定名称是否将在作用域链中找到,以及如果找到,将在哪个对象中找到。例如如果您只查看jsfunction f(x, o) { with (o) { console.log(x); } }
f
的定义,则无法判断with
体中的x
指的是什么。只有在调用f
时,才能确定x
是o.x
还是f
的第一个形式参数。如果您忘记在作为第二个参数传递的对象中定义x
,您不会收到错误 - 相反,您只会得到意外的结果。也不清楚此类代码的实际意图是什么。 - 向前兼容性:使用
with
的代码可能不向前兼容,尤其是在与普通对象以外的其他对象一起使用时,这些对象将来可能会获得更多属性。考虑以下示例如果您在 ECMAScript 5 环境中调用jsfunction f(foo, values) { with (foo) { console.log(values); } }
f([1, 2, 3], obj)
,则with
语句内的values
引用将解析为obj
。但是,ECMAScript 2015 在Array.prototype
上引入了values
属性(因此它将在每个数组上可用)。因此,在升级环境后,with
语句内的values
引用解析为[1, 2, 3].values
,并且很可能会导致错误。在此特定示例中,values
通过Array.prototype[Symbol.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
将转换每个变量查找为属性查找,而代理允许拦截每个属性查找调用。您可以通过组合它们来创建一个动态命名空间。
const namespace = new Proxy(
{},
{
has(target, key) {
// Avoid trapping global properties like `console`
if (key in globalThis) {
return false;
}
// Trap all property lookups
return true;
},
get(target, key) {
return key;
},
},
);
with (namespace) {
console.log(a, b, c); // "a" "b" "c"
}
规范
规范 |
---|
ECMAScript 语言规范 # sec-with-statement |
浏览器兼容性
BCD 表仅在浏览器中加载