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,
},
};