generated from MetaMask/metamask-module-template
-
Notifications
You must be signed in to change notification settings - Fork 4
On programming with Hardened JavaScript
Erik Marks edited this page Aug 2, 2024
·
1 revision
Consider these two implementations of a counter capability:
// Closure
export const makeCapability = () => {
let counter = 0;
return harden({
inc() {
counter += 1;
return counter;
},
});
};
harden(makeCapability);
// Class
export class Capability {
#counter = 0;
constructor() {
harden(this);
}
inc() {
this.#counter += 1;
return this.#counter;
}
}
harden(Capability);
Are they behaviorally equivalent? Despite appearances:
const cap1 = makeCapability();
const cap2 = new Capability();
console.log(cap1.inc() === cap2.inc()); // true
console.log(cap1.inc() === cap2.inc()); // true
cap1.inc();
console.log(cap1.inc() === cap2.inc()); // false
They in fact differ in at least two important respects:
- Methods that depend on private state can be usefully extracted from the closure, but not the class implementation.
-
const { inc } = makeCapability(); inc();
works, butconst { inc } = new Capability(); inc();
throws aTypeError
.
-
- 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.