Skip to content

Instantly share code, notes, and snippets.

@rbuckton
Created March 2, 2018 04:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rbuckton/13733ae5fd7cc5754222d1f1faa8a75a to your computer and use it in GitHub Desktop.
Save rbuckton/13733ae5fd7cc5754222d1f1faa8a75a to your computer and use it in GitHub Desktop.
A rough outline for a userland `@freeze` decorator that does not require "instance finishers".
//
// definition
//
// marker used to indicate a constructor is a freeesing constructor (see below).
const marker = new WeakSet();
export function freeze(descriptor) {
// save the previous constructor
const previousConstructor = descriptor.constructor;
// define the replacement constructor
const freezingConstructor = function (...args) {
// To ensure the correct timing for Object.freeze, new.target must have been previously
// marked.
if (!marker.has(new.target)) throw new TypeError("A subclass must be marked @freeze if its superclass is marked @freeze.");
// construct the instance
const instance = Reflect.construct(previousConstructor, args, new.target);
if (new.target === freezingConstructor) {
// if we are the target of `new`, it is safe to freeze.
Object.freeze(instance);
}
else {
// if we are not the target of `new`, we should only mark all current own properties
// as non-configurable, non-writable without freezing instance
for (const name of Object.getOwnPropertyNames(instance)) {
const descriptor = Object.getOwnPropertyDescriptor(instance, name);
if (descriptor.writable || descriptor.configurable) {
if (descriptor.writable) descriptor.writable = false;
if (descriptor.configurable) descriptor.configurable = false;
Object.defineProperty(instance, name, descriptor);
}
}
for (const symbol of Object.getOwnPropertyNames(instance)) {
const descriptor = Object.getOwnPropertyDescriptor(instance, symbol);
if (descriptor.writable || descriptor.configurable) {
if (descriptor.writable) descriptor.writable = false;
if (descriptor.configurable) descriptor.configurable = false;
Object.defineProperty(instance, symbol, descriptor);
}
}
}
return instance;
};
// mark the new constructor as one that freezes its instance when finished.
marker.add(freezingConstructor);
// wire up the replacement constructor
descriptor.constructor = freezingConstructor;
// add a finisher to freeze the static and prototype sides of the class.
descriptor.finisher = function (constructor) {
Object.freeze(constructor);
Object.freeze(constructor.prototype);
};
return descriptor;
}
//
// usage
//
@freeze
class A {
constructor() {
this.x = 1; // non-frozen in constructor
}
}
Object.isFrozen(new A()); // true
class B extends A {
}
new B(); // TypeError: A subclass must be marked @freeze if its superclass is marked @freeze.
@freeze
class C extends A {
constructor() {
super();
this.y = 1; // still non-frozen in constructor
}
}
Object.isFrozen(new C()); // true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment