What survives
Published
An accessor looks like a property and behaves like a function, which is exactly the sort of thing people expect a code transform to mishandle. Measured against the running engine, accessors are one of the safest constructs in the language: everything about them survives except the name, and the name only moves if you ask for it.
What was measured
The test was not a reading of the transform source. A file containing every common accessor shape was run through the protector and then executed in node, alongside the original, with the two outputs diffed. The shapes covered were: a getter and setter pair in an object literal, a getter and setter pair on a class, an accessor inherited through Object.create, and a property installed with Object.defineProperty using a string key.
Both target versions were measured, because the ES5 target down-levels class syntax and the modern target does not, and that is precisely the sort of difference that would show up here if it existed anywhere.
The result was identical output in every case. The setter still ran its side effect (set total(v){ this._total = v + 1; } still added the one). The getter still computed from the backing field. The inherited accessor still resolved through the prototype. The lazy getter that increments a counter was still invoked exactly once per read, so nothing had quietly memoised or eagerly evaluated it.
The property descriptor survives too
The subtler question is whether an accessor is still an accessor afterwards, or whether it has been flattened into a plain data property that happens to hold the right value at the moment you looked. Those are very different things: a data property stops tracking the backing field the instant anything else writes to it.
Reading the descriptor back with Object.getOwnPropertyDescriptor answers it. After protection the descriptor still reports a function for get, a function for set, and enumerable still true. It is a real accessor, not a snapshot.
Object.keys also returns the same list in the same order, so the accessor is still enumerable in the same position among the object’s own keys. Anything that walks your objects generically continues to see what it saw before.
The name is the part that moves
Renaming identifiers is the core of what a protector does, and a member name is a different category from a local variable. A local is confined to a scope the engine can see completely, so renaming it is always safe. A member name can be referenced from a string, from another file, or from data that arrived over the network, and none of those are visible to the transform.
So member renaming is opt-in, and the engine enforces that: switching it on without also supplying a regexp or a custom mapping fails with a clear message rather than guessing at a default. That is the correct trade, and it means the default configuration does not touch your accessor names at all.
With member renaming switched on and pointed at an accessor, the measurement shows the getter and the setter renamed together, to the same new name, with the dotted reads and writes following them. Semantics hold. The pair does not desynchronise from itself, which is the failure people fear most here and which does not happen.
Where it does break, and it is the familiar asymmetry
What does not follow the rename is any reference to the accessor that is written as a string. Measured on the same object: after renaming total, the dotted access returns the right value and store["total"] returns undefined. The bracket form was never rewritten, so it now names a property that no longer exists.
Object.getOwnPropertyDescriptor(store, "total") fails the same way and slightly worse: it still passes the old string, finds nothing, and returns undefined, so the very next line that reads .get off the result throws instead of returning a wrong answer.
This is not an accessor-specific bug. It is the same asymmetry that applies to every renamed member: the transform rewrites identifier keys, string keys in object literals, and dotted access, and a bracket lookup is not one of those. Accessors simply make it easier to notice, because reflective code that inspects descriptors is exactly the sort of code that reaches for string keys.
What this means in practice
If you are running a default configuration, accessors need no thought. Nothing about them changes, in either target version, and you can stop reading here.
If you switch member renaming on, the rule to apply is the one that applies to all members: reserve the names that anything outside your own dotted code refers to. That includes accessor names read through a bracket, names passed to Object.defineProperty or getOwnPropertyDescriptor, names that appear in serialised data, and names a framework resolves from a template string.
The cheapest way to find these is not a code review. Protect a build, run your existing test suite against the protected output, and let the failures tell you which names crossed a boundary the transform could not see. That is the same advice this site gives for every member-renaming question, and accessors do not need a special version of it.
Frequently asked questions
Do getters and setters still work after obfuscation?
Yes. Measured by running the engine and executing the result, object-literal accessors, class accessors, inherited accessors and properties installed with Object.defineProperty all behave identically before and after protection, on both the ES5 and the modern target.
Is a getter turned into a plain property?
No. Reading the descriptor back after protection still reports a function for get, a function for set, and enumerable still true. It remains a real accessor that recomputes on every read, not a value captured at transform time.
Does a lazy getter still run only when read?
Yes. A getter that increments a counter on each read was invoked exactly once per read after protection, so nothing was eagerly evaluated or memoised by the transform.
Are accessor names renamed by default?
No. Member renaming is opt-in and the engine refuses to run it without an explicit regexp or custom mapping, so a default configuration leaves your accessor names alone.
If I turn member renaming on, do the getter and setter stay in sync?
Yes. The getter and the setter for the same property are renamed together to the same new name, and dotted reads and writes follow. The pair does not desynchronise from itself.
What breaks when an accessor is renamed?
String references to it. A bracket lookup such as store["total"] returns undefined, and Object.getOwnPropertyDescriptor with the old string returns undefined so the next property read throws. Reserve any accessor name that is reached through a string rather than dotted access.
Related reading