Compatibility

Does Obfuscation Break Type Coercion?

Coercion is the part of JavaScript people trust least, so it is a fair thing to check before protecting a build. The measured answer is that protection does not change any of it. There is, however, one configuration that breaks coercion silently rather than loudly, and it is the most important caveat on this page.

The coercion protocol survives protection

When an object is used where a primitive is expected, JavaScript asks the object for one by calling valueOf and toString. Code that defines those methods is relying on a name-based protocol, which is exactly the kind of thing worth measuring against a tool whose main job is renaming things.

The sample defines an object with an amount of 42, a valueOf returning that amount and a toString returning a formatted $42. Used in arithmetic it produces 42; multiplied by two it produces 84; concatenated into a string it produces cost=42; passed to String() it produces $42; compared with > 40 it is true.

All of that was identical across all five configurations, including both targets and the string table. The objects that define only one half of the protocol behave correctly too: an object with only toString coerces to a number through it, and an object with only valueOf uses that in string concatenation.

Default behaviour for plain objects is unchanged as well: a plain object concatenates as [object Object] and produces NaN in arithmetic. Arrays still coerce through join, so an empty array is 0 in arithmetic and a single-element array coerces to that element.

Loose equality and truthiness are untouched

The comparison table people memorise and then double-check stays exactly as it was. null == undefined is true while null === undefined is false. 0 == false, "" == 0 and "1" == 1 are all true. null == 0 is false, which is the one that surprises people, and it stays false.

NaN == NaN remains false while Object.is(NaN, NaN) remains true, and Object.is(-0, 0) remains false. If you have code that deliberately uses Object.is to tell negative zero apart, it keeps working.

Truthiness is stable: the seven falsy values filter out to nothing, and the values that look falsy but are not, empty array, empty object, the string zero and a single space, all remain truthy. Unary coercion is unchanged, so +"42" is 42 and !!"0" is true. The typeof results are identical for every type, including the historical typeof null being object.

Wrapper objects still behave like objects

Wrapper objects are a small trap that some defensive code depends on. new Number(5) has a typeof of object while the primitive is number. It is loosely equal to the primitive but not strictly equal. new Boolean(false) is truthy, because it is an object.

Every one of those held after protection, on both targets. If you have a guard that rejects wrapper objects, or a type check that distinguishes them, protection does not change what it sees.

The caveat that matters: do not rename valueOf or toString

This is the one finding on this page that can bite, and unlike the other compatibility caveats we publish, it fails silently.

Member renaming rewrites property and method names that match your pattern, and it is name-based rather than type-aware. If your pattern matches valueOf or toString, the methods your object defines are renamed. The coercion protocol then finds no method by those names and falls back to the default object behaviour.

We measured it with a pattern of ^(amount|valueOf|toString)$. The object that had produced 42 in arithmetic now produced NaN. The one that had produced cost=42 in string context now produced cost=[object Object]. String() returned [object Object] instead of $42. The comparison that had been true became false. Nothing threw at any of those sites.

That is the whole problem. A comparison flipping from true to false with no exception is the kind of defect that reaches production and then has to be found by bisection. Later in the same run a call to .valueOf() on a wrapper object did throw a TypeError, because that name had been renamed too, but by then several wrong values had already been produced and used.

Renaming the plain data property on its own, with a pattern of ^(amount)$, was completely safe and produced identical output. The rule is precise: rename your data, do not rename the names the language itself calls.

How to configure this safely

Write the member pattern as an explicit anchored list of names your own objects define, and keep protocol method names out of it. valueOf and toString are the two on this page; the same reasoning covers toJSON, which we measured in an earlier pass and which silently disables JSON serialization the same way.

The general principle is worth stating once, because it explains several separate caveats at once. Renaming is safe when the name is only ever reached by code you control. It is unsafe when the name is part of a contract with something else, whether that is the language calling valueOf, JSON.stringify calling toJSON, or your own code reaching a property by string through hasOwnProperty.

A practical check: after enabling member renaming, run your test suite against the protected bundle and pay attention to assertions about formatted output and comparisons, not just to thrown errors. This class of defect produces wrong values rather than crashes, so a test that only checks for exceptions will pass. The Protect Members page documents the pattern syntax.

Frequently asked questions

Does protection change how valueOf and toString coerce objects?

No, provided you are not renaming those method names. We measured an object defining both against five configurations covering both targets and the string table, and every coercion result was identical: 42 in arithmetic, 84 when multiplied, cost=42 in string context and $42 from String().

Does obfuscation change loose equality results?

No. The full comparison table was identical after protection, including null equalling undefined loosely but not strictly, null not equalling 0, NaN not equalling itself, and Object.is distinguishing negative zero. Truthiness and typeof results were unchanged for every type as well.

Why does my object print as [object Object] after protection?

Almost certainly because member renaming matched valueOf or toString. Renaming is name-based and not type-aware, so if your pattern matches those names the methods your object defines are renamed, the language finds no method by the expected name, and it falls back to default object behaviour. Narrow the pattern to exclude protocol method names.

Does renaming valueOf throw an error?

Not at the coercion sites, which is what makes it dangerous. We measured arithmetic producing NaN, string context producing [object Object], and a comparison flipping from true to false, all without an exception. A TypeError did appear later when a renamed name was called explicitly, but several wrong values had already been produced by then.

Is it safe to rename ordinary data properties?

Yes. Renaming just the data property, with a pattern naming only that, produced output identical to the original on both targets. The distinction that matters is whether a name is part of a contract with something outside your code. Data properties your code alone reads are safe; names the language or a library calls are not.

Are wrapper objects like new Number affected?

No. A wrapper object still reports a typeof of object rather than number, is still loosely but not strictly equal to the primitive, and new Boolean(false) is still truthy because it is an object. Any guard that distinguishes wrappers from primitives sees exactly what it saw before protection.

Related reading