I got a problem with calling super inside of the child static method.
const _todos = [];
class Todo {
constructor(title) {
this.title = title;
this.id = Math.round(Math.random() * 100);
_todos.push({title, id: this.id});
console.log(`Todo ID: ${this.id}. DONT FORGET IT!`);
}
static TodoerVersion = '1.8';
static removeTodo(id) {
const todoIndex = _todos.findIndex(t => t.id == id);
_todos.splice(todoIndex, 1);
}
}
class TodoV2 extends Todo {
static addTodo(title) {
super(title);
}
static addDescription(todo, description) {
todo.description = description;
}
static TodoerVersion = '2.0';
};
new TodoV2("play guitar");
Why does it not work?
But if i call super in normal method, it would works just fine.
super is only callable within the constructor function of a class.
Two answers for you:
The question you actually asked.
What I think you should do instead. :-)
Answering the question you asked:
JavaScript is very special in this regard: this has meaning in static methods (as long as you call them correctly, e.g. TodoV2.addTodo("title")): it's the constructor function you called the static method on. And a subclass constructor function inherits from its superclass constructor function (yes, really). So you can access the constructor function for the parent class using Object.getPrototypeOf(this) in the static method:
// This is something unusual that JavaScript actually does support
static addTodo(title) {
const ctor = Object.getPrototypeOf(this);
return new ctor(title);
}
To handle the case where the user may have called addTodo in a way that doesn't set this, you might do something like this to default to TodoV2 if this is undefined:
static addTodo(title) {
const ctor = Object.getPrototypeOf(this ?? TodoV2);
return new ctor(title);
}
What I think you should do instead
You shouldn't be using a static method for this. Instead, define a constructor for TodoV2:
class TodoV2 extends Todo {
constructor(title, description) {
super(title);
this.description = description ?? "";
}
static addTodo(title) { // Why no `description`?
return new this(title); // Or `new TodoV2(title)`
}
}
You might also look into the Symbol.species pattern if you want subclasses to create instances of superclasses.
Related
In python there's something like __call__ for this. Consider the following example:
class MyClass {
__call__() { return 'called!' }
}
const myType = new MyClass();
myType(); // called!
The question is what should I replace __call__ with?
I was doing some research, and most of the answers recommend __proto__, but it doesn't seem to work.
It is not possible out-of-the-box, but you can extend Function, and use the Function constructor to forward a call to __call__. If you have multiple classes that need this feature, extend Function only once into -- let's say -- a Callable class, and then inherit your other classes from that:
class Callable extends Function {
constructor() {
super("...args", "return this.__call__(...args)");
return this.bind(this);
}
}
class Class extends Callable {
__call__() { return 'called!' }
}
let inst = new Class();
console.log(inst());
Background
In JavaScript an object is callable when, and only if, it has the [[Call]] internal slot. But there is (currently) no way to give any given object this slot via JavaScript code. One must start with a function object and extend that.
Adding a constructor, inheritance
The above solution allows the constructor to define properties in the usual way: the constructed object is an instance of the class:
class Callable extends Function {
constructor() {
super("...args", "return this.__call__(...args)");
return this.bind(this);
}
}
class Class extends Callable {
constructor(data) {
super();
this.x = data;
}
__call__() { return 'called!' }
}
let inst = new Class(42);
console.log(inst instanceof Class); // true
console.log(inst.x); // 42
console.log(inst());
You can use constructor.
class Example {
constructor() {
// gets called on class initialization
}
}
Inside the constructor you can also call other methods if you want.
However this won't create an invoke function like using PHP's __invoke if that's what you meant. If that's what you're looking for then I don't know.
I'm trying to wrap class constructor and inject to some logic by using class decorator. Everything worked fine until I have tried to extend wrapped class: Extended class don't have methods in prototype.
function logClass(Class) {
// save a reference to the original constructor
const _class = Class;
// proxy constructor
const proxy = function(...args) {
const obj = new _class(...args);
// ... add logic here
return obj
}
// copy prototype so intanceof operator still works
proxy.prototype = _class.prototype;
// return proxy constructor (will override original)
return proxy;
}
#logClass
class Base {
prop = 5;
test() {
console.log("test")
}
}
class Extended extends Base {
test2() {
console.log("test2")
}
}
var base = new Base()
base.test()
var ext = new Extended()
console.log(ext.prop)
ext.test()
ext.test2() // TypeError: ext.test2 is not a function
Okay so I tried to figure out what is "wrong" with your code, but I was not able to make it work because it didn't typecheck. So, as a last resort, I'm posting a partial answer of my attempt, which works (with some quirks) so I can help other users who are more savvy with TypeScript.
First of all, the quirks: class decorators in TS cannot modify the structure of a type, so if you wanted to, for example, add a method to the decorated class, you would be able to do it but you would have to eat up/suppress unavoidable type errors (TS2339) when calling those methods.
There is a work around for this in this other question: Typescript adding methods with decorator type does not exist, but you would lose this current clean syntax for decorators if you do this.
Now, my solution, taken more or less directly from the documentation:
function logClass<T extends { new(...args: any[]): {} }>(constructor: T) {
return class extends constructor {
constructor(...args: any[]) {
super(args);
// ...add programmatic logic here
// (`super` is the decorated class, of type `T`, here)
}
// ...add properties and methods here
log(message: string) { // EXAMPLE
console.log(`${super.constructor.name} says: ${message}`);
}
}
}
#logClass
class Base {
prop = 5;
test() {
console.log("test");
}
constructor() {}
}
class Extended extends Base {
test2() {
console.log("test2");
}
}
var base = new Base();
base.test();
var ext = new Extended();
console.log(ext.prop);
//base.log("Hello"); // unavoidable type error TS2339
ext.test();
ext.test2();
I was previously rolling my own Javascript OOP but now I'm playing with ES6 and want to use the class defined after definition in a generic way.
Note
Any answer with new in it is not what I'm after.
Pseudo code:
// base.js
class Base {
constructor(arg) {
this.arg = arg;
}
// This is the behaviour I'm after
//afterDefined(cls) {
afterExtended(cls) { // probably a better name
console.log(`Class name ${cls.prototype.name}`);
}
}
// frombase.js
class FromBase extends Base {
constructor({ p1='val1', p2='val2'} = {}) {
super(...arguments);
this.p1 = p1;
this.p2 = p2;
}
}
The output in the console should be:
'Class name FromBase'
So far the only solution I have come up with is to have a static method on Base and call it after the class declaration when I define a new class but I will most likely forget to do this more than once.
Just to be really thorough on why I don't like the static solution; it will force me to import Base in every single file.
Example using a static method (which I don't want) https://jsfiddle.net/nL4atqvm/:
// base.js
class Base {
constructor(arg) {
super(...arguments);
this.arg = arg;
}
// This is the behaviour I'm after
static afterExtended(cls) {
console.log(`Class name ${cls.name}`);
}
}
// frombase.js
class FromBase extends Base {
}
// just after defining the FromBase class
Base.afterExtended(FromBase);
There is no javascript built-in trigger that is calling a method on a class when a subclass is defined that extends from it.
Because you're rolling your own library, you could craft some kind of method the creates and returns a new class that extends a given base class. Maybe check out this answer that may help how to define your classes: Instantiate a JavaScript Object Using a String to Define the Class Name
You could also check how other javascript libraries creates (sub)classes. For example, Ext JS has a ClassManager that you could look into.
http://docs.sencha.com/extjs/6.5.1/classic/Ext.ClassManager.html (docs)
http://docs.sencha.com/extjs/6.5.1/classic/src/ClassManager.js.html (source)
When this question would be about instantiation and not about defining classes, I would say:
afterDefined(cls) {
console.log(`Class name ${this.constructor.name}`);
}
Usage:
let x = new FromBase()
x.afterDefined() // --> Class name FromBase
To get the name of the class, use
static afterDefined(cls) {
console.log(`Class name ${this.name}`);
}
Is this what you're looking for?
class Base {
constructor(arg) { this.arg = arg; }
static afterDefined(cls) {
console.log(`Class name ${this.constructor.name}`);
}
}
Base = new Proxy(Base, {
get: function(target, key, receiver) {
if (typeof target == 'function' && key == 'prototype' && target.name == Base.name) {
Reflect.apply(Base.afterDefined, Reflect.construct(class FromBase {}, []), [])
}
return target[key];
}
});
class FromBase extends Base {}
In order for this to load class names, you will have to embed or forward-declare that information inside of the Proxy receiver prior to extending from it (which means you either need to enumerate ahead of time which classes you'll be inheriting to or to have some function call just prior to that class's declaration).
There are a bunch of other neat total hacks in the same vein such as looking at the source (text) of the file that you just loaded JavaScript from and then parsing that text as if it were JavaScript (people have used this to write Scheme interpreters that can accept new code inside of <script> tags).
If you are a library author intending to target Node, there are even more ways to go about doing this.
// base.js
class Base {
constructor(arg) {
this.arg = arg;
}
// This is the behaviour I'm after
afterDefined(cls) {
console.log(`Class name ${cls}`);
}
}
// frombase.js
class FromBase extends Base {
constructor(arg) {
super(arg)
}
}
let f = new FromBase();
f.afterDefined('text');//this you text or object
have to be aware of is. file loading order, super is an instance of the parent class. good luck.
I am trying to figure out alternative ways to set a static (or class) property an ES6 Class and then change it after new instances of the class are created.
For example, lets say I have a class called Geo, and I need a static property called all that will give me the array of all instances of the Geo class.
This version works:
class Geo {
constructor(name){
this.name = name;
Geo.all.push(this);
}
}
Geo.all = [];
ruby = new Geo("Ruby");
rocks = new Geo("Rocks");
console.log(Geo.all.length); // => 2
I would prefer to not set the property OUTSIDE of the class definition though. I've tried a few things but can't seem to create a static property within the class that I can update from the constructor.
I should also mention I need to be able to do this in the browser (Chrome) without use of Babel or similar.
Here are examples of some things I've tried:
class Geo {
constructor(name){
this.name = name;
Geo.all.push(this);
}
static get all() {
return [];
}
}
ruby = new Geo("Ruby");
rocks = new Geo("Rocks");
console.log(Geo.all.length); // => 0
And another
class Geo {
constructor(name){
this.name = name;
Geo.all.push(this);
}
static all = [];
}
ruby = new Geo("Ruby");
rocks = new Geo("Rocks");
console.log(Geo.all.length); // => error unexpected "="
There's no such thing as static all = [] in ES6. Class instance and static fields are currently stage 3 proposals which can be used via a transpiler, e.g. Babel. There's already existing implementation in TypeScript that may be incompatible with these proposals in some way, yet static all = [] is valid in TS and ES.Next.
Geo.all = [];
is valid and preferable way to do this in ES6. The alternative is getter/setter pair - or only a getter for read-only property:
class Geo {
static get all() {
if (!this._all)
this._all = [];
return this._all;
}
constructor() { ... }
}
Tracking instances in static property can't generally be considered a good pattern and will lead to uncontrollable memory consumption and leaks (as it was mentioned in comments).
This works for me for static properties.
class NeoGeo {
constructor() {
}
static get topScore () {
if (NeoGeo._topScore===undefined) {
NeoGeo._topScore = 0; // set default here
}
return NeoGeo._topScore;
}
static set topScore (value) {
NeoGeo._topScore = value;
}
}
And your example:
class NeoGeo {
constructor() {
NeoGeo.addInstance(this);
console.log("instance count:" + NeoGeo.all.length);
}
static get all () {
if (NeoGeo._all===undefined) {
NeoGeo._all = [];
}
return NeoGeo._all;
}
static set all (value) {
NeoGeo._all = value;
}
static addInstance(instance) {
// add only if not already added
if (NeoGeo.all.indexOf(instance)==-1) {
NeoGeo.all.push(instance);
}
}
}
Note: In the getter you could also check for the existence of the property using the in keyword or the hasOwnProperty keyword.
static get topScore () {
if (!("_topScore" in NeoGeo)) {
NeoGeo._topScore = 0; // set default here
}
return NeoGeo._topScore;
}
And using hasOwnProperty:
static get topScore () {
if (NeoGeo.hasOwnProperty("_topScore")==false) {
NeoGeo._topScore = 0; // set default here
}
return NeoGeo._topScore;
}
I recently had a similar issue of creating static classes.
I first tried it with constant class variables, but Chrome debugger threw an error.
So I defined the class variables 'static', also the getter methods.
Worked in Chrome.
class TestClass {
//static properties.
static _prop1 = [ 'A', 'B', 'C'];
static _prop2 = true;
static _prop3 = 'some String';
//constructor. Commented out because the class only has static elements.
//constructor () {}
//Getters.
static get prop1 () {
return this._prop1;
}
static get prop2 () {
return this._prop2;
}
static get prop3 () {
return this._prop3;
}
}
The only way to properly add a getter is to extend the class and use that extended class.
class Basic {
get firstGetter() {
return 'firstGetter'
}
}
class ExtendedClass extends Basic {
get firstGetter() {
return 'updatedFirstGetter'
}
}
}
Update your node to the version 12 or up and that's it ;)
Is there any way to determine if a subclass implements a constructor from within a static method (in a base class)?
I'm trying to write a static create method (that acts like the new keyword) that by default works by passing attribute values as a properties object:
class Person extends Class {
greet() { return 'hello from ' + this.name; }
}
var p = Person.create({name: 'world'}; // create a new Person object and set its `name` property to `'world'`
console.log(p.greet()); // => "hello from world"
but hands off to the class' constructor if it has one:
class Person2 extends Class {
constructor(name) {
super();
this.name = name;
}
greet() { return 'hello from ' + this.name; }
}
var p = Person2.create('world');
console.log(p.greet()); // => "hello from world"
I'm stuck at finding out if the subclass defines its own constructor..
class Class {
static create(...args) {
let has_ctor = ?? // true iff the current subclass defines a constructor..
if (has_ctor) {
// let the constructor handle everything
return new this(...args);
} else {
// assume that `args` contains exactly 1 pojo that defines instance variables to be overridden..
var instance = new this();
let props = args[0];
for (let prop in props) instance[prop] = props[prop];
return instance;
}
}
}
is this even possible?
Seems like it would be much easier to do
class Class {
static create(...args) {
// let the constructor handle everything
return new this(...args);
}
constructor(props){
Object.assign(this, props);
}
}
then if things override the constructor, then can choose to pass props to super() or to assign them manually themselves.
Just to answer your original question
Is there a way to discover if a javascript 6 class defines its own constructor?
No, there is not. Every class does have its own constructor, because a "class" basically is just the constructor function.
If a class definition does not include a constructor method, then it is automatically supplied by the language (see ยง14.5.14); either as
constructor(...args){ super (...args);}
if there is a super class or as
constructor(){ }
if there is none. The result is not distinguishable from a class where such a constructor was explicitly declared.