Symbol.unscopables

The Symbol.unscopables static data property represents the well-known symbol @@unscopables. The with statement looks up this symbol on the scope object for a property containing a collection of properties that should not become bindings within the with environment.

Try it

Value

The well-known symbol @@unscopables.

Property attributes of Symbol.unscopables
Writable no
Enumerable no
Configurable no

Description

The @@unscopables symbol (accessed via Symbol.unscopables) can be defined on any object to exclude property names from being exposed as lexical variables in with environment bindings. Note that when using strict mode, with statements are not available, and this symbol is likely not needed.

Setting a property of the @@unscopables object to true (or any truthy value) will make the corresponding property of the with scope object unscopable and therefore won't be introduced to the with body scope. Setting a property to false (or any falsy value) will make it scopable and thus appear as lexical scope variables.

When deciding whether x is unscopable, the entire prototype chain of the @@unscopables property is looked up for a property called x. This means if you declared @@unscopables as a plain object, Object.prototype properties like toString would become unscopable as well, which may cause backward incompatibility for legacy code assuming those properties are normally scoped (see an example below). You are advised to make your custom @@unscopables property have null as its prototype, like Array.prototype[@@unscopables] does.

This protocol is also utilized by DOM APIs, such as Element.prototype.append().

Examples

Scoping in with statements

The following code works fine in ES5 and below. However, in ECMAScript 2015 and later, the Array.prototype.keys() method was introduced. That means that inside a with environment, "keys" would now be the method and not the variable. That's why the @@unscopables symbol was introduced. A built-in @@unscopables setting is implemented as Array.prototype[@@unscopables] to prevent some of the Array methods being scoped into the with statement.

js

var keys = [];

with (Array.prototype) {
  keys.push("something");
}

Unscopables in objects

You can also set @@unscopables for your own objects.

js

const obj = {
  foo: 1,
  bar: 2,
  baz: 3,
};

obj[Symbol.unscopables] = {
  // Make the object have `null` prototype to prevent
  // `Object.prototype` methods from being unscopable
  __proto__: null,
  // `foo` will be scopable
  foo: false,
  // `bar` will be unscopable
  bar: true,
  // `baz` is omitted; because `undefined` is falsy, it is also scopable (default)
};

with (obj) {
  console.log(foo); // 1
  console.log(bar); // ReferenceError: bar is not defined
  console.log(baz); // 3
}

Avoid using a non-null-prototype object as @@unscopables

Declaring @@unscopables as a plain object without eliminating its prototype may cause subtle bugs. Consider the following code working before @@unscopables:

js

const character = {
  name: "Yoda",
  toString: function () {
    return "Use with statements, you must not";
  },
};

with (character) {
  console.log(name + ' says: "' + toString() + '"'); // Yoda says: "Use with statements, you must not"
}

To preserve backward compatibility, you decided to add an @@unscopables property when adding more properties to character. You may naïvely do it like:

js

const character = {
  name: "Yoda",
  toString: function () {
    return "Use with statements, you must not";
  },
  student: "Luke",
  [Symbol.unscopables]: {
    // Make `student` unscopable
    student: true,
  },
};

However, the code above now breaks:

js

with (character) {
  console.log(name + ' says: "' + toString() + '"'); // Yoda says: "[object Undefined]"
}

This is because when looking up character[Symbol.unscopables].toString, it returns Object.prototype.toString(), which is a truthy value, thus making the toString() call in the with() statement reference globalThis.toString() instead — and because it's called without a this, this is undefined, making it return [object Undefined].

Even when the method is not overridden by character, making it unscopable will change the value of this.

js

const proto = {};
const obj = { __proto__: proto };

with (proto) {
  console.log(isPrototypeOf(obj)); // true; `isPrototypeOf` is scoped and `this` is `proto`
}

proto[Symbol.unscopables] = {};

with (proto) {
  console.log(isPrototypeOf(obj)); // TypeError: Cannot convert undefined or null to object
  // `isPrototypeOf` is unscoped and `this` is undefined
}

To fix this, always make sure @@unscopables only contains properties you wish to be unscopable, without Object.prototype properties.

js

const character = {
  name: "Yoda",
  toString: function () {
    return "Use with statements, you must not";
  },
  student: "Luke",
  [Symbol.unscopables]: {
    // Make the object have `null` prototype to prevent
    // `Object.prototype` methods from being unscopable
    __proto__: null,
    // Make `student` unscopable
    student: true,
  },
};

Specifications

Specification
ECMAScript Language Specification
# sec-symbol.unscopables

Browser compatibility

BCD tables only load in the browser

See also