Declaring @@unscopables
as a plain object without eliminating its prototype may cause subtle bugs. Consider the following code working before @@unscopables
:
const character = {
name: "Yoda",
toString: function () {
return "Use with statements, you must not";
},
};
with (character) {
console.log(name + ' says: "' + toString() + '"');
}
To preserve backward compatibility, you decided to add an @@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]: {
student: true,
},
};
However, the code above now breaks:
with (character) {
console.log(name + ' says: "' + toString() + '"');
}
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));
}
proto[Symbol.unscopables] = {};
with (proto) {
console.log(isPrototypeOf(obj));
}
To fix this, always make sure @@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]: {
__proto__: null,
student: true,
},
};