Symbol.unscopables
Baseline Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since September 2015.
The Symbol.unscopables
static data property represents the well-known symbol 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 Symbol.unscopables
.
Property attributes of Symbol.unscopables |
|
---|---|
Writable | no |
Enumerable | no |
Configurable | no |
Description
The [Symbol.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 [Symbol.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 [Symbol.unscopables]
property is looked up for a property called x
. This means if you declared [Symbol.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 [Symbol.unscopables]
property have null
as its prototype, like Array.prototype[Symbol.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, the Array.prototype.values()
method was introduced. That means that inside a with
environment, "values" would now be the Array.prototype.values()
method and not the variable outside the with
statement.
var values = [];
with (values) {
// If [Symbol.unscopables] did not exist, values would become
// Array.prototype.values starting with ECMAScript 2015.
// And an error would have occurred.
values.push("something");
}
The code containing with (values)
caused some websites to malfunction in Firefox when Array.prototype.values()
was added (Firefox Bug 883914). Furthermore, this implies that any future array method addition may be breaking if it implicitly changes the with
scope. Therefore, the [Symbol.unscopables]
symbol was introduced and implemented on Array
as Array.prototype[Symbol.unscopables]
to prevent some of the Array methods being scoped into the with
statement.
Unscopables in objects
You can also set [Symbol.unscopables]
for your own objects.
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 [Symbol.unscopables]
Declaring [Symbol.unscopables]
as a plain object without eliminating its prototype may cause subtle bugs. Consider the following code working before [Symbol.unscopables]
:
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 [Symbol.unscopables]
property when adding more properties to character
. You may naïvely do it like:
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:
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
.
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 [Symbol.unscopables]
only contains properties you wish to be unscopable, without Object.prototype
properties.
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