The following is an interpretation of "protected" state exposed through decorators:
NOTE: this has been adapted from https://github.com/tc39/proposal-unified-class-features/blob/master/friend.js
// protected.js
const protectedMembers = new WeakMap();
export function Protected({ key, kind, placement, descriptor }) {
if (typeof key !== "privatename") throw new TypeError("Only members with private names can be inherited with @Protected.");
if (kind !== "method" && kind !== "field") throw new TypeError(`Unexpected member kind: ${kind}`);
// create a new private name for the member.
const storageName = new PrivateName();
const storageKey = storageName.key;
return {
key,
kind: "method",
placement,
extras: [{ key: storageKey, kind, placement, descriptor }],
finisher(ctor) {
let placements = protectedMembers.get(ctor);
if (!placements) protectedMembers.set(ctor, placements = new Map());
let members = placements.get(placement);
if (!members) placements.set(placement, new Map());
members.set(key.toString(), storageName);
}
};
}
export function Inherit({ key, kind, placement }) {
if (typeof key !== "privatename") throw new TypeError("Only members with private names can be inherited with @Inherit.");
if (kind !== "field") throw new TypeError(`Unexpected member kind: ${kind}`);
let superName;
return {
key,
kind: "method",
placement,
descriptor: {
get() { return superName.get(this); },
set(value) { superName.set(this, value); }
},
finisher(ctor) {
const keyString = key.toString();
for (let current = Object.getPrototypeOf(ctor); current !== null; current = Object.getPrototypeOf(ctor)) {
const placements = protectedMembers.get(current);
if (placements !== undefined) {
const members = placements.get(placement);
if (members !== undefined) {
superName = members.get(keyString);
if (superName !== undefined) {
break;
}
}
}
}
if (superName === undefined) throw new TypeError("Member does not inherit a @Protected member.");
}
};
}
Protected members can then be declared using the @Protected
decorator and inherited using the
@Inherit
decorator:
// main.js
import { Protected, Inherit } from "./protected.js";
class Super {
@Protected #foo = 1;
@Protected #bar() { return 2; }
}
class Sub extends Super {
@Inherit #foo;
@Inherit #bar;
method() { console.log(this.#foo, this.#bar); }
}