ES6: Accessing static member variables in classes created from Mixin [duplicate] - javascript

This question already has answers here:
JS & ES6: Access static fields from within class
(2 answers)
Closed 5 years ago.
I have created a class as per the official examples from MDN(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes)
Here is my code
class A {
}
const Mixer = BaseClass => class extends BaseClass {
static val = 10;
}
class B extends Mixer(A) {
myMethod() {
// Need some way to access the static member "val"
}
}
How do I access "val"?
Without mixin(ie class B extends A, and val being a static in class A) I could have done "A.val".
In this scenario, Mixer.val does not work and as per my understanding B extends from an anonymous class, so there is no way to access the super class by name.
Edit: I put the question wrongly. My real problem was to access val in the Mixer itself. Accessing val in B is straight forward, as pointed out my the answers.
For example
const Mixer = BaseClass => class extends BaseClass {
static val = 10;
myMethod2() {
// log the val member
}
}

This was already discussed on SO numerous times, but this case likely needs some explanation. Generally, static properties and methods can be accessed from instance methods with this.constructor:
class B extends Mixer(A) {
myMethod() {
console.log(this.constructor.val);
}
}
Where this.constructor === B, but this won't be true in classes that inherit from B.
There is no reason to refer to parent anonymous class directly in B, because val is inherited from it through prototype chain. This is the purpose of class inheritance.

That is because class fields are not yet in the ECMAScript standard. Your code works perfectly fine if you compile it with BabelJS and paste it to the browser console afterwards.
class B extends Mixer(A) {
myMethod() {
console.log(B.val); // logs 10
}
}
If you need to use ES2015 a quick trick is to use setters and getters.
class A {
}
var val = 10;
const Mixer = BaseClass => class extends BaseClass {
static get val() {
return val;
}
static set val(newVal) {
val = newVal;
}
}
class B extends Mixer(A) {
myMethod() {
// Need some way to access the static member "val"
}
}
// Use it
B.val; // 10
B.val = 15;
B.val; // 15

The es6 do not support static attribute, I dont know what babel do with the static val,but the truth is that what you test dont base on es6 specification. So you may use the static function to do the test

Related

Javascript class composition : access attribute of secondary class in attribute of main class

Let's take this exemple :
class A {
attrA = 3;
}
class B {
constructor() {
this.a = new A;
}
attrB = this.a.attrA;
methB() { console.log(this.a.attrA);}
}
const test = new B;
console.log(test.attrB);
test.methB();
I can access the class A attribute through method of class B, but I can't use it with attribute of class B, I have an "undefined" error.
The only way to do this is :
class A {
attrA = 3;
}
class B {
constructor() {
this.a = new A;
this.z = this.a.attrA
}
methB() { console.log(this.a.attrA);}
}
const test = new B;
console.log(test.z);
test.methB();
Why I need to put the attribute in the constructor and not the method?
Thanks!
You can think of class fields as being hoisted, meaning this:
class Example {
constructor() {/*...*/}
property = "value";
}
Is "actually" this:
class Example {
property = "value";
constructor() {/*...*/}
}
Or (similar to) this:
class Example {
constructor() {
this.property = "value";
/*...*/
}
}
Further, identifiers are resolved upon access. So by the time you execute test.methB(), test.a has been initialized, allowing the method to correctly resolve this.a.attrA. It works the same as this code:
let variable = null;
const logVariable = () => console.log(variable); // You may think this logs null, but ...
variable = "value";
logVariable(); // ... it actually logs `variable`'s value at the time of calling.
As you have observed, mixing property initializations from within the constructor and using field initializers may be confusing. Therefore, a more intuitive way to write your code would be:
class A {
attrA = 3;
}
class B {
a = new A;
attrB = this.a.attrA;
constructor() {}
methB() {
console.log(this.a.attrA);
}
}
const test = new B;
console.log(test.attrB); // Works now!
test.methB();
Personal recommendations:
Declare all instance and class fields.
Prefer field initializers.
Only reassign/initialize fields in the constructor for non-default values.
You may want to read on for more details for a better technical understanding.
Class syntax
Classes are just uncallable constructor functions:
class Example {}
console.log(typeof Example); // "function"
Example(); // Error: cannot be invoked without `new`
This is a quirk of the ES6 class syntax. Technically, class constructors are still "callable", but only internally (which happens when using new). Otherwise constructors would serve no purpose.
Another aspect of the class syntax is the ability to call super() in the constructor. This only comes into play when a class inherits, but that comes with yet its own quirks:
You cannot use this before calling super:
class A {};
class B extends A {
constructor() {
this; // Error: must call super before accessing this
super();
}
}
new B();
Reason being, before calling super no object has been created, despite having used new and being in the constructor.
The actual object creation happens at the base class, which is in the most nested call to super. Only after calling super has an object been created, allowing the use of this in the respective constructor.
Class fields
With the addition of instance fields in ES13, constructors became even more complicated: Initialization of those fields happens immediately after the object creation or the call to super, meaning before the statements that follow.
class A /*no heritage*/ {
property = "a";
constructor() {
// Initializing instance fields
// ... (your code)
}
}
class B extends A {
property = "b";
constructor() {
super();
// Initializing instance fields
// ... (your code)
}
}
Further, those property "assignments" are actually no assignments but definitions:
class Setter {
set property(value) {
console.log("Value:", value);
}
}
class A extends Setter {
property = "from A";
}
class B extends Setter {
constructor() {
super();
// Works effectively like class A:
Object.defineProperty(this, "property", { value: "from B" });
}
}
class C extends Setter {
constructor() {
super();
this.property = "from C";
}
}
new A(); // No output, expected: "Value: from A"
new B(); // No output, expected: "Value: from B"
new C(); // Output: "Value: from C"
Variables
Identifiers are only resolved upon access, allowing this unintuitive code:
const logNumber = () => console.log(number); // References `number` before its declaration, but ...
const number = 0;
logNumber(); // ... it is resolved here, making it valid.
Also, identifiers are looked up from the nearest to the farthest lexical environment. This allows variable shadowing:
const variable = "initial-value";
{
const variable = "other-value"; // Does NOT reassign, but *shadows* outer `variable`.
console.log(variable);
}
console.log(variable);

Getting the name of a class on which a static function has been called

So my question is as follows: Can I get the name of a class on which a function got called? To illustrate that problem I will give a short code example.
class Base {
static getClassName() {
// get the caller class here
}
}
class Extended extends Base { }
Base.getClassName(); // Base
Extended.getClassName(); // Extended
Can anyone tell me how to do this, or say if this is even possible.
The following code should work:
class Base {
static getClassName() {
return this.name;
}
}
class Extended extends Base {}
console.log(Base.getClassName()); // Base
console.log(Extended.getClassName()); // Extended
In a static method, this refers to the class object itself.
In ES6, functions have a read-only name field (doc). Since classes are functions, you can simply use MyClass.name to statically get the class name.
To get the class name of functions at runtime, reference the Function.name of the constructor function.
class Base {
getClassName() {
return this.constructor.name;
}
}
class Extended extends Base {}
console.log(Base.name);
console.log(Extended.name);
const base = new Base();
console.log(base.getClassName());
const extended = new Extended();
console.log(extended.getClassName());

Ecmascript 6 Class: access constructor created method of super class

I'm creating methods inside a constructor, to work with private values - this seems to be the suggested pattern to create private properties and limit the access to them.
Now I inherit from that class and within the constructor of the derived class I create another method, trying to access that. Unfortunately I get an exception TypeError: ... is not a function.
What to do? I can use the constructor as exactly that and just create a new object, but that would not be related to its classes and copying all the properties of the class and the super class etc. seems quite annoying and not like the right way. So again, what to do?
Here is an example:
class ClassA {
constructor() {
this.getValue = () => "private value";
}
}
class ClassB extends ClassA {
constructor() {
super();
this.getValue = () => `${super.getValue()}, second private value`;
}
}
const a = new ClassA();
const b = new ClassB();
console.log(`a.getValue = ${a.getValue()}`);
console.log(`b.getValue = ${b.getValue()}`);
Fiddle Check the console. The outcome I'd expect is "private value, second private value".
If someone can give me a hint, or a not super shitty workaround, I'd appreciate that.
Thanks, fea
super refers to the classes prototype. As your methods are not prototype methods but instance methods, it will unfortunately not work. However you could store the original function reference in a variable:
const orig = this.getValue.bind(this);
this.getValue = () => `${orig()}, second private value`;
However i personally rather like the _ pattern:
class ClassA {
constructor() {
this._private = "test";
}
getValue(){ return this._private; }
}
class ClassB extends ClassA {
constructor() {
super();
}
getValue(){
return `${super.getValue()}, second private value`;
}
}

Split a Javascript class (ES6) over multiple files?

I have a Javascript class (in ES6) that is getting quite long. To organize it better I'd like to split it over 2 or 3 different files. How can I do that?
Currently it looks like this in a single file:
class foo extends bar {
constructor(a, b) {} // Put in file 1
methodA(a, b) {} // Put in file 1
methodB(a, b) {} // Put in file 2
methodC(a, b) {} // Put in file 2
}
Thanks!
When you create a class
class Foo extends Bar {
constructor(a, b) {
}
}
you can later add methods to this class by assigning to its prototype:
// methodA(a, b) in class Foo
Foo.prototype.methodA = function(a, b) {
// do whatever...
}
You can also add static methods similarly by assigning directly to the class:
// static staticMethod(a, b) in class Foo
Foo.staticMethod = function(a, b) {
// do whatever...
}
You can put these functions in different files, as long as they run after the class has been declared.
However, the constructor must always be part of the class declaration (you cannot move that to another file). Also, you need to make sure that the files where the class methods are defined are run before they are used.
Here's my solution. It:
uses regular modern classes and .bind()ing, no prototype. (EDIT: Actually, see the comments for more on this, it may not be desirable.)
works with modules. (I'll show an alternative option if you don't use modules.)
supports easy conversion from existing code.
yields no concern for function order (if you do it right).
yields easy to read code.
is low maintenance.
unfortunately does not play well with static functions in the same class, you'll need to split those off.
First, place this in a globals file or as the first <script> tag etc.:
BindToClass(functionsObject, thisClass) {
for (let [ functionKey, functionValue ] of Object.entries(functionsObject)) {
thisClass[functionKey] = functionValue.bind(thisClass);
}
}
This loops through an object and assigns and binds each function, in that object, by its name, to the class. It .bind()'s it for the this context, so it's like it was in the class to begin with.
Then extract your function(s) from your class into a separate file like:
//Use this if you're using NodeJS/Webpack. If you're using regular modules,
//use `export` or `export default` instead of `module.exports`.
//If you're not using modules at all, you'll need to map this to some global
//variable or singleton class/object.
module.exports = {
myFunction: function() {
//...
},
myOtherFunction: function() {
//...
}
};
Finally, require the separate file and call BindToClass like this in the constructor() {} function of the class, before any other code that might rely upon these split off functions:
//If not using modules, use your global variable or singleton class/object instead.
let splitFunctions = require('./SplitFunctions');
class MySplitClass {
constructor() {
BindToClass(splitFunctions, this);
}
}
Then the rest of your code remains the same as it would if those functions were in the class to begin with:
let msc = new MySplitClass();
msc.myFunction();
msc.myOtherFunction();
Likewise, since nothing happens until the functions are actually called, as long as BindToClass() is called first, there's no need to worry about function order. Each function, inside and outside of the class file, can still access any property or function within the class, as usual.
I choose to have all privte variables/functions in an object called private, and pass it as the first argument to the external functions.
this way they have access to the local variables/functions.
note that they have implicit access to 'this' as well
file: person.js
const { PersonGetAge, PersonSetAge } = require('./person_age_functions.js');
exports.Person = function () {
// use privates to store all private variables and functions
let privates={ }
// delegate getAge to PersonGetAge in an external file
// pass this,privates,args
this.getAge=function(...args) {
return PersonGetAge.apply(this,[privates].concat(args));
}
// delegate setAge to PersonSetAge in an external file
// pass this,privates,args
this.setAge=function(...args) {
return PersonSetAge.apply(this,[privates].concat(args));
}
}
file: person_age_functions.js
exports.PersonGetAge =function(privates)
{
// note: can use 'this' if requires
return privates.age;
}
exports.PersonSetAge =function(privates,age)
{
// note: can use 'this' if requires
privates.age=age;
}
file: main.js
const { Person } = require('./person.js');
let me = new Person();
me.setAge(17);
console.log(`I'm ${me.getAge()} years old`);
output:
I'm 17 years old
note that in order not to duplicate code on person.js, one can assign all functions in a loop.
e.g.
person.js option 2
const { PersonGetAge, PersonSetAge } = require('./person_age_functions.js');
exports.Person = function () {
// use privates to store all private variables and functions
let privates={ }
{
// assign all external functions
let funcMappings={
getAge:PersonGetAge,
setAge:PersonSetAge
};
for (const local of Object.keys(funcMappings))
{
this[local]=function(...args) {
return funcMappings[local].apply(this,[privates].concat(args));
}
}
}
}
You can add mixins to YourClass like this:
class YourClass {
ownProp = 'prop'
}
class Extension {
extendedMethod() {
return `extended ${this.ownProp}`
}
}
addMixins(YourClass, Extension /*, Extension2, Extension3 */)
console.log('Extended method:', (new YourClass()).extendedMethod())
function addMixins() {
var cls, mixin, arg
cls = arguments[0].prototype
for(arg = 1; arg < arguments.length; ++ arg) {
mixin = arguments[arg].prototype
Object.getOwnPropertyNames(mixin).forEach(prop => {
if (prop == 'constructor') return
if (Object.getOwnPropertyNames(cls).includes(prop))
throw(`Class ${cls.constructor.name} already has field ${prop}, can't mixin ${mixin.constructor.name}`)
cls[prop] = mixin[prop]
})
}
}
TypeScript Solution
foo-methods.ts
import { MyClass } from './class.js'
export function foo(this: MyClass) {
return 'foo'
}
bar-methods.ts
import { MyClass } from './class.js'
export function bar(this: MyClass) {
return 'bar'
}
class.ts
import * as barMethods from './bar-methods.js'
import * as fooMethods from './foo-methods.js'
const myClassMethods = { ...barMethods, ...fooMethods }
class _MyClass {
baz: string
constructor(baz: string) {
this.baz = baz
Object.assign(this, myClassMethods);
}
}
export type MyClass = InstanceType<typeof _MyClass> &
typeof myClassMethods;
export const MyClass = _MyClass as unknown as {
new (
...args: ConstructorParameters<typeof _MyClass>
): MyClass;
};
My solution is similar to the one by Erez (declare methods in files and then assign methods to this in the constructor), but
it uses class syntax instead of declaring constructor as a function
no option for truly private fields - but this was not a concern for this question anyway
it does not have the layer with the .apply() call - functions are inserted into the instance directly
one method per file: this is what works for me, but the solution can be modified
results in more concise class declaration
1. Assign methods in constructor
C.js
class C {
constructor() {
this.x = 1;
this.addToX = require('./addToX');
this.incX = require('./incX');
}
}
addToX.js
function addToX(val) {
this.x += val;
return this.x;
}
module.exports = addToX;
incX.js
function incX() {
return this.addToX(1);
}
module.exports = incX;
2. Same, but with instance fields syntax
Note that this syntax is a Stage 3 proposal as of now.
But it works in Node.js 14 - the platform I care about.
C.js
class C {
x = 1;
addToX = require('./addToX');
incX = require('./incX');
}
Test
const c = new C();
console.log('c.incX()', c.incX());
console.log('c.incX()', c.incX());

Referencing static members from instance method on dynamically extended JS class

I have a base ES6 class that I dynamically extend given a configuration object, like so:
class Model {
constructor () {
// ...
}
save () {
// ...
}
}
function createModelFromConfig (config) {
const Impl = class extends Model {};
Object.assign(Impl, config);
return Impl;
}
const User = createModelFromConfig({store: new DbStore()});
In the save() method on the abstract Model, I'd like to reference the static object store, which will exist on the class that extends Model. This means, of course, that I need to reference a static member but the extended class is anonymous.
Just in a quick test using the Chrome console, I tried
function X () {}
X.prototype.doSomething = function () { console.log(this.constructor); };
function Y () {}
Y.prototype = Object.create(X.prototype);
new Y().doSomething(); // function X () {}
I don't know if this is a reliable test, but it appears that this.constructor does not reference the Impl that I extended, but instead the original base class, which isn't helpful.
A less elegant way is to add Impl.prototype.Impl = Impl; so I can use this.Impl.store in my save function, but it'd be preferable if I could access the static members of the Impl class without this.
Is my prototypal test in the console inadequate? Or is there any other way to access the constructor class in an instance method from an inherited method?
In my testing, I've concluded that Y.prototype = Object.create(X.prototype); is not an adequate equivalent to the ES6 extends implementation.
In running in the Node REPL:
class X {
constructor () {}
save () { console.log(this.constructor.z); }
}
class Y extends X {}
Y.z = 'z';
new Y().save(); // 'z'

Categories

Resources