with

Deprecated: This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible; see the compatibility table at the bottom of this page to guide your decision. Be aware that this feature may cease to work at any time.

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

¥Note: Use of the with statement is not recommended, as it may be the source of confusing bugs and compatibility issues, makes optimization impossible, and is forbidden in strict mode. The recommended alternative is to assign the object whose properties you want to access to a temporary variable.

with 语句扩展了语句的作用域链。

¥The with statement extends the scope chain for a statement.

语法

¥Syntax

js
with (expression)
  statement
expression

将给定表达式添加到计算语句时使用的作用域链。表达式两边的括号是必需的。

statement

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

描述

¥Description

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

¥There are two types of identifiers: a qualified identifier and an unqualified identifier. An unqualified identifier is one that does not indicate where it comes from.

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

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

¥Normally, an unqualified identifier is resolved by searching the scope chain for a variable with that name, while a qualified identifier is resolved by searching the prototype chain of an object for a property with that name.

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

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

¥One exception to this is the global object, which sits on top of the scope chain, and whose properties automatically become global variables that can be referred to without qualifiers.

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

with 语句在其语句主体求值期间将给定对象添加到此作用域链的头部。每个非限定名称将首先在对象内搜索(通过 in 检查),然后再在上层作用域链中搜索。

¥The with statement adds the given object to the head of this scope chain during the evaluation of its statement body. Every unqualified name would first be searched within the object (through a in check) before searching in the upper scope chain.

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

¥Note that if the unqualified reference refers to a method of the object, the method is called with the object as its this value.

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

该对象可能有一个 @@unscopables 属性,它定义了不应添加到作用域链的属性列表(为了向后兼容)。有关详细信息,请参阅 Symbol.unscopables 文档。

¥The object may have an @@unscopables property, which defines a list of properties that should not be added to the scope chain (for backward compatibility). See the Symbol.unscopables documentation for more information.

使用 with 语句的原因包括保存一个临时变量并通过避免重复冗长的对象引用来减小文件大小。然而,with 语句不可取的原因还有很多:

¥The reasons to use a with statement include saving one temporary variable and reducing file size by avoiding repeating a lengthy object reference. However, there are far more reasons why with statements are not desirable:

  • 表现: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[@@unscopables] 进行作用域,因此它仍然可以正确解析为 values 参数。如果它没有被定义为不可作用域,人们就会发现这将是一个难以调试的问题。

示例

¥Examples

使用 with 语句

¥Using the with statement

以下 with 语句指定 Math 对象是默认对象。with 语句后面的语句引用 PI 属性以及 cossin 方法,但不指定对象。JavaScript 假定这些引用是 Math 对象。

¥The following with statement specifies that the Math object is the default object. The statements following the with statement refer to the PI property and the cos and sin methods, without specifying an object. JavaScript assumes the Math object for these references.

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

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

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

¥Avoiding the with statement by destructuring properties into the current scope

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

¥You can usually avoid using with through property destructuring. Here we create an extra block to mimic the behavior of with creating an extra scope — but in actual usage, this block can usually be omitted.

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 语句

¥Avoiding the with statement by using an IIFE

如果你生成的表达式必须多次重复使用长名称引用,并且你的目标是消除表达式中的冗长名称,则可以将该表达式封装在 IIFE 中并提供长名称作为参数。

¥If you're producing an expression that must reuse a long-named reference multiple times, and your goal is to eliminate that lengthy name within your expression, you can wrap the expression in an IIFE and provide the long name as an argument.

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

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

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

¥Creating dynamic namespaces using the with statement and a proxy

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

¥with will transform every variable lookup to a property lookup, while Proxies allow trapping every property lookup call. You can create a dynamic namespace by combining them.

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"
}

规范

Specification
ECMAScript Language Specification
# sec-with-statement

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看