How to make ES6 class final (non-subclassible) - javascript

Assume we have:
class FinalClass {
...
}
How to modify it to make
class WrongClass extends FinalClass {
...
}
or
new WrongClass(...)
to generate an exception? Perhaps the most obvious solution is to do the following in the FinalClass's constructor:
if (this.constructor !== FinalClass) {
throw new Error('Subclassing is not allowed');
}
Does anyone have a more cleaner solution instead of repeating these lines in each class that supposed to be final (probably with a decorator)?

Inspect this.constructor in the constructor of FinalClass and throw if it is not itself. (Borrowing inspection of the this.constructor instead of this.constructor.name from #Patrick Roberts.)
class FinalClass {
constructor () {
if (this.constructor !== FinalClass) {
throw new Error('Subclassing is not allowed')
}
console.log('Hooray!')
}
}
class WrongClass extends FinalClass {}
new FinalClass() //=> Hooray!
new WrongClass() //=> Uncaught Error: Subclassing is not allowed
Alternatively, with support, use new.target. Thanks #loganfsmyth.
class FinalClass {
constructor () {
if (new.target !== FinalClass) {
throw new Error('Subclassing is not allowed')
}
console.log('Hooray!')
}
}
class WrongClass extends FinalClass {}
new FinalClass() //=> Hooray!
new WrongClass() //=> Uncaught Error: Subclassing is not allowed
______
As you say, you could also achieve this behaviour with a decorator.
function final () {
return (target) => class {
constructor () {
if (this.constructor !== target) {
throw new Error('Subclassing is not allowed')
}
}
}
}
const Final = final(class A {})()
class B extends Final {}
new B() //=> Uncaught Error: Subclassing is not allowed
As Patrick Roberts shared in the comments the decorator syntax #final is still in proposal. It is available with Babel and babel-plugin-transform-decorators-legacy.

constructor.name is easy enough to spoof. Just make the subclass the same name as the superclass:
class FinalClass {
constructor () {
if (this.constructor.name !== 'FinalClass') {
throw new Error('Subclassing is not allowed')
}
console.log('Hooray!')
}
}
const OopsClass = FinalClass
;(function () {
class FinalClass extends OopsClass {}
const WrongClass = FinalClass
new OopsClass //=> Hooray!
new WrongClass //=> Hooray!
}())
Better to check the constructor itself:
class FinalClass {
constructor () {
if (this.constructor !== FinalClass) {
throw new Error('Subclassing is not allowed')
}
console.log('Hooray!')
}
}
const OopsClass = FinalClass
;(function () {
class FinalClass extends OopsClass {}
const WrongClass = FinalClass
new OopsClass //=> Hooray!
new WrongClass //=> Uncaught Error: Subclassing is not allowed
}())

Related

Javascript arrow func in subclass cannot be found by super class

What I would like to achieve:
Essentially, I would like my subclass to have a lexically-bound this function. However, I would like the super class to check that the subclass has an instantiation of this lexically-bound function.
This is how I would prefer to write the code, but it doesn't work:
class Animal {
constructor(type) {
this.animalType = type;
if (!(this.bark instanceof Function)) {
throw new Error('Found no bark');
}
}
}
class Dog extends Animal {
bark = () => {
console.log('woof');
}
}
let max = new Dog('dog')
max.bark();
Yet this works:
class Animal {
constructor(type) {
this.animalType = type;
if (!(this.bark instanceof Function)) {
throw new Error('Found no bark');
}
}
}
class Dog extends Animal {}
Dog.prototype.bark = () => {
console.log('woof');
}
let max = new Dog('dog')
max.bark();
and this works:
class Animal {
constructor(type) {
this.animalType = type;
if (!(this.bark instanceof Function)) {
throw new Error('Found no bark');
}
}
bark = () => {
console.log('woof');
}
}
class Dog extends Animal {}
let max = new Dog('dog')
max.bark();
Could someone please explain why my first example is failing. It seems to me that bark() isn't in the prototype chain somehow but I'm not sure why.

Custom String class

I am looking for a 1-2 punch.
I'd like to typecast custom strings.
Within runtime I'd like to be able to know the type of a string different from a primitive string.
Here's the code:
class TZDatabaseName extends String {
constructor(...args) {
super(...args);
return this;
}
}
expect(new TZDatabaseName('Asia/Tokyo') instanceof String).toBeTruthy();
expect(new TZDatabaseName('Asia/Tokyo') instanceof TZDatabaseName).toBeTruthy();
expect(new TZDatabaseName('Asia/Tokyo')).toEqual('Asia/Tokyo');
I would like all three of the checks below to pass.
I also have been messing with this method of casting strings as well but I have no way of checking in runtime the type of the variable.
export abstract class TZDatabaseName extends String {
public static MAKE(s: string): TZDatabaseName {
if (!s.match(/^\w+\/\w+$/)) throw new Error('invalid TZDatabaseName');
return s as any;
}
private __TZDatabaseNameFlag;
}
Actually, ignore my previous comments about the primitive datatype and object being different, I just tested this myself, and all tests pass? ...
class TZDatabaseName extends String {
constructor(...args) {
super(...args);
return this;
}
}
describe('TZDatabaseName', function() {
it('Instance of String', function() {
expect(new TZDatabaseName('Asia/Tokyo') instanceof String).toBeTruthy();
});
it('Instance of TZDatabaseName', function() {
expect(new TZDatabaseName('Asia/Tokyo') instanceof TZDatabaseName).toBeTruthy();
});
it('Equal to Primitive Type', function() {
expect(new TZDatabaseName('Asia/Tokyo')).toEqual('Asia/Tokyo');
});
});
describe('More TZDatabaseName', function() {
it('Primitive Instance of TZDatabaseName', function() {
expect(''
instanceof TZDatabaseName).toBeFalsy();
});
it('Primitive Instance of String', function() {
expect(''
instanceof String).toBeFalsy();
});
it('String Instance of TZDatabaseName', function() {
expect(String('') instanceof TZDatabaseName).toBeFalsy();
});
});
// Jasmine htmlReporter
(function() {
var env = jasmine.getEnv();
env.addReporter(new jasmine.HtmlReporter());
env.execute();
}());
<link rel="stylesheet" href="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css" />
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script>
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script>
Revisiting this a couple years later, it seems I just want to do this:
class TZDatabaseName extends String {
constructor(...args) {
super(...args);
if (!s.match(/^\w+\/\w+$/)) {
throw new Error('invalid TZDatabaseName');
}
return this;
}
}

Return constructor of generic type in TypeScript

I'm struggling to define how to write TypeScipt code which says that function return constructor of generic type. There are plenty of examples around about how to pass constructor of the generic type, but not how to return.
Please check the following example:
This is part of the abstract class:
getModel(): (new () => T) {
throw new Error('Method not implemented.'); // Error because don't know how to fix it
}
When in derived class I'm trying to implement it like this:
getModel(): typeof User {
return User;
}
I have the following error:
Type '() => typeof User' is not assignable to type '() => new () => User'.
I could skip implementation in the derived class if I knew how to specify in the abstract class.
So question is - how to specify on an abstract class level that method returns constructor of the generic type and I can skip implementation of this method at child level class? Or maybe I specify return signature not correctly on the abstract class level?
EDIT:
Please check the strange problem. Class A and B differ only by the presence of explicit constructor. And in RealA doesn't work and RealB works the same getModel() method.
class A {
a = '';
constructor(a: string) {
}
}
class B {
a = '';
static test(): void {
console.log('I do work');
}
}
abstract class Base<T> {
Prop: T;
constructor(TCreator: { new (): T; }) {
this.Prop = new TCreator();
}
getModel(): (new () => T) {
throw new Error('Method not implemented.'); // Error because don't know how to fix it
}
}
class RealA extends Base<A> {
getModel(): typeof A { // doesn't work - compilation error
return A;
}
}
class RealB extends Base<B> {
getModel(): typeof B { // works
return B;
}
}
var test = new RealA(A); // compile error
var test2 = new RealB(B)
For RealA class the same error
() => typeof A' is not assignable to type '() => new () => A'
The error is expected as the constructor for class A has a required argument. The abstract class constrains the constructor to be passed it to have no arguments (new () => T).
The simple solution is to remove the constructor to A.
If you want to be able to pass in classes that have constructors that require arguments you will need to change the definition of the base class to capture the constructor type, and have the constructor take in those required arguments (using tuples in rest parameters)
class A {
a = '';
constructor(a: string) {
}
}
class B {
a = '';
static test(): void {
console.log('I do work');
}
}
type ArgumentTypes<T> = T extends new (...a: infer A) => any? A : []
abstract class Base<T extends new (...a: any[])=> any> {
Prop: InstanceType<T>;
constructor(TCreator: T, ...a: ArgumentTypes<T>) {
this.Prop = new TCreator(...a);
}
getModel(): T {
throw new Error('Method not implemented.'); // Error because don't know how to fix it
}
}
class RealA extends Base<typeof A> {
getModel(): typeof A { // doesn't work - compilation error
return A;
}
}
class RealB extends Base<typeof B> {
getModel(): typeof B { // works
return B;
}
}
var test = new RealA(A, ""); // ok
var test2 = new RealB(B)

how to create methods in methods in es6 class

Good day,
I dont know if am can explain this well for you to help but i will like to use a an ES6 class to create an object that can be called like this.
var = varaibles
obj = objects
obj.var
obj.var.method
obj.var.var.method
obj.method.var
and so on.
I can only do one step
obj.var && obj.method
i will kind appreciate if one can help me here thanks
this is what i have done
class Table extends someClass {
constructor() {
super();
this.column = {
sort: () => {
console.log("firing");
},
resize: () => {
console.log("firing");
}
};
this.cells = {
edit: () => {
console.log("firing");
}
};
}
myMethods() {
//BLAH
}
}
From what I understood, here is my solution.
If I return a object full of methods, I can use that object as I like.
class someClass {
// this is a parent method
Parent() {
console.log(`From a Parent`)
}
// a getter that returns an object
get parentWithChild() {
return {
child() {
console.log(`From a Child`)
}
}
}
// a function that returns an object
Methods() {
return {
child() {
console.log(`From a Child`)
}
}
}
}
const cool = new someClass();
cool.Parent(); // From a Parent
cool.parentWithChild.child(); // From a Child
cool.Methods().child(); // From a Child
You can use similar pattern on the extended class too.

What's happening when inheriting a Proxy of Class (Function) in JavaScript

I want to inherit a function with proxied constructor, like SubB below;
const Base = function () {};
Base.prototype.baseMethod = function () { return 'base method'; }
class SubA extends (new Proxy(Base, {})) {
subMethod () { return 'sub method'; }
}
const handler = { construct: (target, args) => new target(...args) };
class SubB extends (new Proxy(Base, handler)) {
subMethod () { return 'sub method'; }
}
However, it does not work collectly; Subclass methods seems not to be bound in SubB.
(new SubA()).baseMethod(); //=> "base method"
(new SubB()).baseMethod(); //=> "base method"
(new SubA()).subMethod(); //=> "sub method"
(new SubB()).subMethod();
//=> Uncaught TypeError: (intermediate value).subMethod is not a function
What's happening in class SubB and how can I fix it (or is it possible)?
You are ignoring new.target, which is why the instance that your proxied constructor creates always inherits from the Base (the target in the proxy handler) only, and not from the SubB.
You should use Reflect.construct as the default action of the construct trap:
const handler = {
construct(target, args, newTarget) {
return Reflect.construct(target, args, newTarget);
}
};
class SubB extends (new Proxy(Base, handler)) {
subMethod () { return 'sub method'; }
}

Categories

Resources