Created
March 2, 2018 04:42
-
-
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".
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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