Skip to content

On programming with Hardened JavaScript

Erik Marks edited this page Aug 2, 2024 · 1 revision

Classes vs. objects and closures

Consider these two implementations of a counter capability:

// ClosureexportconstmakeCapability=()=>{letcounter=0;returnharden({inc(){counter+=1;returncounter;},});};harden(makeCapability);// ClassexportclassCapability{
#counter =0;constructor(){harden(this);}inc(){this.#counter +=1;returnthis.#counter;}}harden(Capability);

Are they behaviorally equivalent? Despite appearances:

constcap1=makeCapability();constcap2=newCapability();console.log(cap1.inc()===cap2.inc());// trueconsole.log(cap1.inc()===cap2.inc());// truecap1.inc();console.log(cap1.inc()===cap2.inc());// false

They in fact differ in at least two important respects:

  1. Methods that depend on private state can be usefully extracted from the closure, but not the class implementation.
    • const { inc } = makeCapability(); inc(); works, but const { inc } = new Capability(); inc(); throws a TypeError.
  2. Class instances can be "hijacked" from a distance.
    • const instance = new Capability(); (new Capability()).inc.call(instance) affects an instance without access to any of its methods.
    • This is because #-private fields are class-private, not instance-private, whereas the closure's private state is instance-private.

Agoric's documentation touches on these topics here. They have also explored the idea of using JavaScript classes as Exo definitions (ref).

Thanks to @gibson042 and @mhofman for elucidating these things.

Clone this wiki locally