with

已弃用:此功能不再推荐。尽管某些浏览器可能仍然支持它,但它可能已从相关的 Web 标准中删除,可能正在被删除的过程中,或者可能仅出于兼容性目的而保留。避免使用它,并尽可能更新现有代码;请参阅此页面底部的兼容性表,以指导您的决策。请注意,此功能可能随时停止工作。

注意:不建议使用with语句,因为它可能是令人困惑的错误和兼容性问题的原因,使得优化无法进行,并且在严格模式中是被禁止的。推荐的替代方法是将您要访问其属性的对象分配给一个临时变量。

with语句扩展了语句的范围链。

语法

js
with (expression)
  statement
表达式

将给定的表达式添加到评估语句时使用的作用域链中。表达式周围的括号是必需的。

语句

任何语句。要执行多个语句,请使用语句({ ... })对这些语句进行分组。

描述

标识符有两种类型:限定标识符和非限定标识符。非限定标识符是不指示其来源的标识符。

js
foo; // unqualified identifier
foo.bar; // bar is a qualified identifier

通常,非限定标识符通过在作用域链中搜索具有该名称的变量来解析,而限定标识符通过在对象的原型链中搜索具有该名称的属性来解析。

js
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

一个例外是全局对象,它位于作用域链的顶部,其属性自动成为全局变量,可以无需限定符即可引用。

js
console.log(globalThis.Math === Math); // true

with语句在评估其语句体期间将给定的对象添加到此作用域链的头部。每个非限定名称都将在对象内(通过in检查)进行搜索,然后再在较高的作用域链中搜索。

请注意,如果非限定引用引用了对象的某个方法,则该方法将以对象作为其this值来调用。

js
with ([1, 2, 3]) {
  console.log(toString()); // 1,2,3
}

对象可能具有[Symbol.unscopables]属性,该属性定义了不应添加到作用域链中的属性列表(用于向后兼容)。有关更多信息,请参阅Symbol.unscopables文档。

使用with语句的原因包括节省一个临时变量并通过避免重复冗长的对象引用来减小文件大小。但是,有更多理由说明为什么with语句不可取

  • 性能:with语句强制首先搜索指定的对象以进行所有名称查找。因此,在with块中,所有不是指定对象成员的标识符都将更慢地找到。此外,优化器无法对每个非限定标识符引用什么做出任何假设,因此它必须在每次使用标识符时重复相同的属性查找。
  • 可读性:with语句使人类读者或 JavaScript 编译器难以确定非限定名称是否将在作用域链中找到,以及如果找到,将在哪个对象中找到。例如
    js
    function f(x, o) {
      with (o) {
        console.log(x);
      }
    }
    
    如果您只查看f的定义,则无法判断with体中的x指的是什么。只有在调用f时,才能确定xo.x还是f的第一个形式参数。如果您忘记在作为第二个参数传递的对象中定义x,您不会收到错误 - 相反,您只会得到意外的结果。也不清楚此类代码的实际意图是什么。
  • 向前兼容性:使用with的代码可能不向前兼容,尤其是在与普通对象以外的其他对象一起使用时,这些对象将来可能会获得更多属性。考虑以下示例
    js
    function f(foo, values) {
      with (foo) {
        console.log(values);
      }
    }
    
    如果您在 ECMAScript 5 环境中调用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语句指定Math对象是默认对象。with语句之后的语句引用PI属性以及cossin方法,而无需指定对象。JavaScript 为这些引用假设Math对象。

js
let a, x, y;
const r = 10;

with (Math) {
  a = PI * r * r;
  x = r * cos(PI);
  y = r * sin(PI / 2);
}

通过将属性解构到当前作用域来避免使用 with 语句

通常,您可以通过属性解构来避免使用with。在这里,我们创建一个额外的块来模拟with创建额外作用域的行为 - 但在实际使用中,此块通常可以省略。

js
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中并将长名称作为参数提供。

js
const objectHavingAnEspeciallyLengthyName = { foo: true, bar: false };

if (((o) => o.foo && !o.bar)(objectHavingAnEspeciallyLengthyName)) {
  // This branch runs.
}

使用 with 语句和代理创建动态命名空间

with将转换每个变量查找为属性查找,而代理允许拦截每个属性查找调用。您可以通过组合它们来创建一个动态命名空间。

js
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 表仅在浏览器中加载

另请参阅