with

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

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

with 语句为一条语句扩展了作用域链。

语法

js
with (expression)
  statement
表达式

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

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 会将每个变量查找转换为属性查找,而 代理(Proxies)允许捕获每个属性查找调用。你可以通过结合它们来创建动态命名空间。

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® 2026 语言规范
# sec-with-statement

浏览器兼容性

另见