Why are my two javascript class instances not identical? - javascript

I have a custom "Enum" class TableSourceType (parent class Enum is given below):
import Enum from './../../components/enum.js';
export default class TableSourceType extends Enum {}
TableSourceType.csv = new TableSourceType('csv');
TableSourceType.sqLite = new TableSourceType('sqLite');
TableSourceType.mySql = new TableSourceType('mySql');
Furhtermore, I have "two" object instances foo and baa and would expect both instances to be identical and the same as TableSourceType.sqlite. However, following equality comparisons yield false:
foo === baa
foo.constructor === baa.constructor
If I compare the names of the instances I get true:
foo.name === baa.name
I already checked that I only have a single source code file that contains the class "TableSourceType". That ES6 class is imported with
import TableSourceType from '../notebooks/treez/src/data/table/tableSourceType.js'
=>Why do I get two different constructors for the "same" import?
Starting from my main html file, I have two <script type="module"> blocks.
The second script block is added dynamically at runtime in order to inject some user defined code and to save some stuff in a global variable.
The comparison takes place in the first ("static") script block. Maybe that somehow causes the instances not to be identical?
=> How can I ensure equality?
=> Where can I find more information to better understand that equality issue?
Actually I would like to use instances of my custom class in a switch statement:
switch (this.sourceType) {
case TableSourceType.csv:
this.__showAndHideCompontentsForCsv();
break;
case TableSourceType.sqLite:
this.__showAndHideCompontentsForSqLite();
break;
default:
var message = 'The TableSourceType "' + this.sourceType + '" is not yet implemented.';
throw new Error(message);
}
That switch statement fails. I would expect this.sourceType and TableSourceType.sqLite to be equal but they are not.
If it is not possible to ensure equality for instances in different script blocks (?) ... is it possible to implement something like custom "equals" and "hashcode" methods in JavaScript?
If so, I would try to tell TableSourceType to define its equality only based on the name property of the instances.
Here is my custom Enum class:
export default class Enum {
static get values(){
var keys = Object.keys(this).filter(key=>!key.startsWith('__'));
return keys.map(key=>this[key]);
}
static get names(){
return this.values.map((value)=>value.name);
}
static get importLocation(){
return this.__importLocation;
}
static forName(name){
for(var type of this.values){
if(type.name === name){
return type;
}
}
throw new Error('Unknown value "' + name + '"');
}
constructor(name){
this.name = name;
if(!this.constructor.__importLocation){
this.constructor.__importLocation = this.determineImportLocation();
}
}
toString(){
return this.name;
}
determineImportLocation(){
var stack = new Error().stack;
var lastLine = stack.split('\n').pop();
var startIndex = lastLine.indexOf('/src/');
var endIndex = lastLine.indexOf('.js:') + 3;
return lastLine.substring(startIndex, endIndex);
}
}
A work around would be to use the name property in the switch statement:
switch (this.sourceType.name) {
case TableSourceType.csv.name:
this.__showAndHideCompontentsForCsv();
break;
case TableSourceType.sqLite.name:
this.__showAndHideCompontentsForSqLite();
break;
default:
var message = 'The TableSourceType "' + this.sourceType + '" is not yet implemented.';
throw new Error(message);
}
However, I would prefer the original version of the switch statement.

I expected the content of my custom class file to be executed only once, because the import already has been resolved before. Thanks to Dani R I found out that the code is actually executed twice.
Following adapted code of TableSourceType works for me:
import Enum from './../../components/enum.js';
export default class TableSourceType extends Enum {}
if(window.TableSourceType){
TableSourceType = window.TableSourceType;
} else {
TableSourceType.csv = new TableSourceType('csv');
TableSourceType.sqLite = new TableSourceType('sqLite');
TableSourceType.mySql = new TableSourceType('mySql');
window.TableSourceType = TableSourceType;
}
If there is a more elegant way to ensure equality, please let me know.

Related

Create class declaration programmatically in javascript?

Eventually what I need is to build a class declaration at runtime providing dynamic parameters to a property annotation:
import {Type} from "external-module";
export default class TypeWrapper {
#Type(() => '{this part of the class declaration should be changable at runtime}')
data
}
I have a feeling that this should be possible to achieve but can't figure out a proper way yet.
As a proof of concept I was trying to do something like below:
let MyClass = eval('(class MyClass{})')
let myClass = new MyClass()
That works, however MyClass needs to define some imports:
let MyClass = eval('import {Type} from "external-module"' +
'(class MyClass{})')
That one fails with "Cannot use import statement outside a module" which is quite expected.
Another approach I tried is to load a module from string:
var moduleData = '' +
'import module from "./module/path/file.js"\n' +
'\n' +
'export default class MyClass {\n' +
'}\n' +
'\n';
var b64moduleData = "data:text/javascript;base64," + btoa(moduleData);
let MyClass = await import(b64moduleData)
But it fails with "Cannot find module", suggesting it assumes b64moduleData is a path rather than module data itself.
Anyone has any suggestions?
Normally, for this type of thing I would use a class factory:
import { Type } from 'some-module';
function createClass(parameters) {
return class {
....
}
}
const MyClass = createClass(...);
I'm unsure if this fits your particular use case based on the details provided.
EDIT: As a side-note, as far as I know, you cannot construct modules from a string, which is what your code is doing, and what the compiler is complaining about.

Javascript polymorphism: how to access static attributes of inheriting class in static method of ("abstract") base class?

I would like to have a base class and an inheriting class. The base class should provide some general functionality which depends on the properties of the inheriting class.
=>How can I access the properties of the inheriting class in the base class?
Below is some example to illustrate the question (My question is not how to define an enum in JavaScript. The example is just for illustration.).
Example base class:
export default class Enum {
constructor(name){
this.name = name;
}
}
Enum.values = function(){
return Object.values(INHERITING_CLASS);
};
Enum.forName = function(name){
for(var enumValue of INHERITING_CLASS.values){
if(enumValue.name === name){
return enumValue;
}
}
throw new Error('Unknown value "' + name + '"');
}
Example inheriting class:
import Enum from './enum.js';
export default class ColumnType extends Enum {
constructor(name, clazz){
super(name);
this.associatedClass = clazz;
}
}
ColumnType.Integer = new ColumnType('Integer', Number);
ColumnType.Double = new ColumnType('Double', Number);
ColumnType.String = new ColumnType('String', String);
I want to be able to access the static values of ColumnType with
ColumnType.values()
where the values function is provided by the base class Enum. Some for method forName.
If I would use "Enum" for the placeholder INHERITING_CLASS, the result is not correct.
=> How do I know that ColumnType is the currently inheriting class while being in the scope of the definition of the Enum class?
Edit
Here is a related question:
Get parent class name from child with ES6?
Their answer uses instance.constructor. However, in my static method I don't have an instance.
It is easier than I thought. When calling the static method ColumnType.values(), inside the values function this will actually provide the wanted Class as context:
export default class Enum {
constructor(name){
this.name = name;
}
}
Enum.values = function(){
return Object.values(this);
};
Enum.forName = function(name){
for(var enumValue of this.values){
if(enumValue.name === name){
return enumValue;
}
}
throw new Error('Unknown value "' + name + '"');
}
I think what you're looking for is the prototype unless I misread the question
ColumnType.prototype.values()

Why don't ES6 classes support static properties? [duplicate]

I want to implement constants in a class, because that's where it makes sense to locate them in the code.
So far, I have been implementing the following workaround with static methods:
class MyClass {
static constant1() { return 33; }
static constant2() { return 2; }
// ...
}
I know there is a possibility to fiddle with prototypes, but many recommend against this.
Is there a better way to implement constants in ES6 classes?
Here's a few things you could do:
Export a const from the module. Depending on your use case, you could just:
export const constant1 = 33;
And import that from the module where necessary. Or, building on your static method idea, you could declare a static get accessor:
const constant1 = 33,
constant2 = 2;
class Example {
static get constant1() {
return constant1;
}
static get constant2() {
return constant2;
}
}
That way, you won't need parenthesis:
const one = Example.constant1;
Babel REPL Example
Then, as you say, since a class is just syntactic sugar for a function you can just add a non-writable property like so:
class Example {
}
Object.defineProperty(Example, 'constant1', {
value: 33,
writable : false,
enumerable : true,
configurable : false
});
Example.constant1; // 33
Example.constant1 = 15; // TypeError
It may be nice if we could just do something like:
class Example {
static const constant1 = 33;
}
But unfortunately this class property syntax is only in an ES7 proposal, and even then it won't allow for adding const to the property.
class Whatever {
static get MyConst() { return 10; }
}
let a = Whatever.MyConst;
Seems to work for me.
I'm using babel and the following syntax is working for me:
class MyClass {
static constant1 = 33;
static constant2 = {
case1: 1,
case2: 2,
};
// ...
}
MyClass.constant1 === 33
MyClass.constant2.case1 === 1
Please consider that you need the preset "stage-0".
To install it:
npm install --save-dev babel-preset-stage-0
// in .babelrc
{
"presets": ["stage-0"]
}
Update for stage:
it was moved on stage-3.
Update Babel 7:
As per Babel 7 stage presets are deprecated.
The Babel plugin to use is #babel/plugin-proposal-class-properties.
npm i --save-dev #babel/plugin-proposal-class-properties
{
"plugins": ["#babel/plugin-proposal-class-properties"]
}
Note: This plugin is included in #babel/preset-env
In this document it states:
There is (intentionally) no direct declarative way to define either prototype data properties (other than methods) class properties, or instance property
This means that it is intentionally like this.
Maybe you can define a variable in the constructor?
constructor(){
this.key = value
}
It is also possible to use Object.freeze on you class(es6)/constructor function(es5) object to make it immutable:
class MyConstants {}
MyConstants.staticValue = 3;
MyConstants.staticMethod = function() {
return 4;
}
Object.freeze(MyConstants);
// after the freeze, any attempts of altering the MyConstants class will have no result
// (either trying to alter, add or delete a property)
MyConstants.staticValue === 3; // true
MyConstants.staticValue = 55; // will have no effect
MyConstants.staticValue === 3; // true
MyConstants.otherStaticValue = "other" // will have no effect
MyConstants.otherStaticValue === undefined // true
delete MyConstants.staticMethod // false
typeof(MyConstants.staticMethod) === "function" // true
Trying to alter the class will give you a soft-fail (won't throw any errors, it will simply have no effect).
Maybe just put all your constants in a frozen object?
class MyClass {
constructor() {
this.constants = Object.freeze({
constant1: 33,
constant2: 2,
});
}
static get constant1() {
return this.constants.constant1;
}
doThisAndThat() {
//...
let value = this.constants.constant2;
//...
}
}
You can create a way to define static constants on a class using an odd feature of ES6 classes. Since statics are inherited by their subclasses, you can do the following:
const withConsts = (map, BaseClass = Object) => {
class ConstClass extends BaseClass { }
Object.keys(map).forEach(key => {
Object.defineProperty(ConstClass, key, {
value: map[key],
writable : false,
enumerable : true,
configurable : false
});
});
return ConstClass;
};
class MyClass extends withConsts({ MY_CONST: 'this is defined' }) {
foo() {
console.log(MyClass.MY_CONST);
}
}
Like https://stackoverflow.com/users/2784136/rodrigo-botti said, I think you're looking for Object.freeze(). Here's an example of a class with immutable statics:
class User {
constructor(username, age) {
if (age < User.minimumAge) {
throw new Error('You are too young to be here!');
}
this.username = username;
this.age = age;
this.state = 'active';
}
}
User.minimumAge = 16;
User.validStates = ['active', 'inactive', 'archived'];
deepFreeze(User);
function deepFreeze(value) {
if (typeof value === 'object' && value !== null) {
Object.freeze(value);
Object.getOwnPropertyNames(value).forEach(property => {
deepFreeze(value[property]);
});
}
return value;
}
I did this.
class Circle
{
constuctor(radius)
{
this.radius = radius;
}
static get PI()
{
return 3.14159;
}
}
The value of PI is protected from being changed since it is a value being returned from a function. You can access it via Circle.PI. Any attempt to assign to it is simply dropped on the floor in a manner similar to an attempt to assign to a string character via [].
You could use import * as syntax. Although not a class, they are real const variables.
Constants.js
export const factor = 3;
export const pi = 3.141592;
index.js
import * as Constants from 'Constants.js'
console.log( Constants.factor );
You can make the "constants" read-only (immutable) by freezing the class. e.g.
class Foo {
static BAR = "bat"; //public static read-only
}
Object.freeze(Foo);
/*
Uncaught TypeError: Cannot assign to read only property 'BAR' of function 'class Foo {
static BAR = "bat"; //public static read-only
}'
*/
Foo.BAR = "wut";
Here is one more way you can do
/*
one more way of declaring constants in a class,
Note - the constants have to be declared after the class is defined
*/
class Auto{
//other methods
}
Auto.CONSTANT1 = "const1";
Auto.CONSTANT2 = "const2";
console.log(Auto.CONSTANT1)
console.log(Auto.CONSTANT2);
Note - the Order is important, you cannot have the constants above
Usage
console.log(Auto.CONSTANT1);
The cleanest way I've found of doing this is with TypeScript - see How to implement class constants?
class MyClass {
static readonly CONST1: string = "one";
static readonly CONST2: string = "two";
static readonly CONST3: string = "three";
}
Just declare your variables as private and use a get method to retrieve them.
class MyClass {
#myConst = 'Something';
static #anotherConst = 'Something Else';
get myConst() {
return this.#myConst; // instance method
}
static get anotherConst() {
return MyClass.#anotherConst; // static method
}
}
let myClass = new MyClass();
console.log( myClass.myConst + ' is not ' + MyClass.anotherConst );
Users cannot change the original variable, and you can write the class to use the get methods rather than the private variables themselves.
One pattern that I use to expose error codes, i.e.,
I have many constants inside the module
I may not want to expose all constants to callers
I do not want to provide 1 static constant for one exposed constant
// inside the module
const Errors = {
INTERNAL: 100,
EMPTY_QUEUE: 101,
UNKNOWN_COMMAND: 102,
OK: 200,
MOVE: 201,
CREATE_DOT: 202,
PIXEL_MAPPING: 203
}
Object.freeze(Errors);
class PlotterError extends Error {
// use constant inside the module
code = Errors.INTERNAL;
constructor(message, code) {
super(message);
this.name = 'PlotterError';
this.code = code
}
}
// expose via static constant
Class Plotter {
.....
static get ERRORS() {
return Errors;
}
....
export Plotter;
// module ends
// in the caller
import {Plotter} from ...
try {
this.plotter.execute();
} catch(error) {
if(error.code == Plotter.ERRORS.EMPTY_QUEUE) {
//
}
}
We can also decide to expose only the constants we want by breaking the constants acr two objects.
If you are comfortable mixing and matching between function and class syntax you can declare constants after the class (the constants are 'lifted') . Note that Visual Studio Code will struggle to auto-format the mixed syntax, (though it works).
class MyClass {
// ...
}
MyClass.prototype.consts = {
constant1: 33,
constant2: 32
};
mc = new MyClass();
console.log(mc.consts.constant2);
Adding up to other answers you need to export the class to use in a different class. This is a typescript version of it.
//Constants.tsx
const DEBUG: boolean = true;
export class Constants {
static get DEBUG(): boolean {
return DEBUG;
}
}
//Anotherclass.tsx
import { Constants } from "Constants";
if (Constants.DEBUG) {
console.log("debug mode")
}
If trying to make a const/variable static to a class; try using the hash (#) to define a place holder, than a function to access it.
class Region {
// initially empty, not accessible from outside
static #empty_region = null;
/*
Make it visible to the outside and unchangeable
[note] created on first call to getter.
*/
static EMPTY() {
if (!this.#empty_region)
this.#empty_region = new Region(0, 0, 0, 0);
return this.#empty_region;
}
#reg = {x0:0, y0:0, x1:0, y1:0};
constructor(x0, y0, x1, y1) {
this.setRegion(x0, y0, x1, y1);
}
// setters/getters
}
Implementation:
let someRegion = Region.EMPTY();
let anotherRegion = Region.EMPTY();
Here You Go!
const Status = Object.freeze(class Status {
static Disabled = 0
static Live = 1
})

Class variable using Class syntax in Javascript [duplicate]

Currently in ES5 many of us are using the following pattern in frameworks to create classes and class variables, which is comfy:
// ES 5
FrameWork.Class({
variable: 'string',
variable2: true,
init: function(){
},
addItem: function(){
}
});
In ES6 you can create classes natively, but there is no option to have class variables:
// ES6
class MyClass {
const MY_CONST = 'string'; // <-- this is not possible in ES6
constructor(){
this.MY_CONST;
}
}
Sadly, the above won't work, as classes only can contain methods.
I understand that I can this.myVar = true in constructor…but I don't want to 'junk' my constructor, especially when I have 20-30+ params for a bigger class.
I was thinking of many ways to handle this issue, but haven't yet found any good ones. (For example: create a ClassConfig handler, and pass a parameter object, which is declared separately from the class. Then the handler would attach to the class. I was thinking about WeakMaps also to integrate, somehow.)
What kind of ideas would you have to handle this situation?
2018 update:
There is now a stage 3 proposal - I am looking forward to make this answer obsolete in a few months.
In the meantime anyone using TypeScript or babel can use the syntax:
varName = value
Inside a class declaration/expression body and it will define a variable. Hopefully in a few months/weeks I'll be able to post an update.
Update: Chrome 74 now ships with this syntax working.
The notes in the ES wiki for the proposal in ES6 (maximally minimal classes) note:
There is (intentionally) no direct declarative way to define either prototype data properties (other than methods) class properties, or instance property
Class properties and prototype data properties need be created outside the declaration.
Properties specified in a class definition are assigned the same attributes as if they appeared in an object literal.
This means that what you're asking for was considered, and explicitly decided against.
but... why?
Good question. The good people of TC39 want class declarations to declare and define the capabilities of a class. Not its members. An ES6 class declaration defines its contract for its user.
Remember, a class definition defines prototype methods - defining variables on the prototype is generally not something you do.
You can, of course use:
constructor(){
this.foo = bar
}
In the constructor like you suggested. Also see the summary of the consensus.
ES7 and beyond
A new proposal for ES7 is being worked on that allows more concise instance variables through class declarations and expressions - https://esdiscuss.org/topic/es7-property-initializers
Just to add to Benjamin's answer — class variables are possible, but you wouldn't use prototype to set them.
For a true class variable you'd want to do something like the following:
class MyClass {}
MyClass.foo = 'bar';
From within a class method that variable can be accessed as this.constructor.foo (or MyClass.foo).
These class properties would not usually be accessible from to the class instance. i.e. MyClass.foo gives 'bar' but new MyClass().foo is undefined
If you want to also have access to your class variable from an instance, you'll have to additionally define a getter:
class MyClass {
get foo() {
return this.constructor.foo;
}
}
MyClass.foo = 'bar';
I've only tested this with Traceur, but I believe it will work the same in a standard implementation.
JavaScript doesn't really have classes. Even with ES6 we're looking at an object- or prototype-based language rather than a class-based language. In any function X () {}, X.prototype.constructor points back to X.
When the new operator is used on X, a new object is created inheriting X.prototype. Any undefined properties in that new object (including constructor) are looked up from there. We can think of this as generating object and class properties.
Babel supports class variables in ESNext, check this example:
class Foo {
bar = 2
static iha = 'string'
}
const foo = new Foo();
console.log(foo.bar, foo.iha, Foo.bar, Foo.iha);
// 2, undefined, undefined, 'string'
In your example:
class MyClass {
const MY_CONST = 'string';
constructor(){
this.MY_CONST;
}
}
Because of MY_CONST is primitive https://developer.mozilla.org/en-US/docs/Glossary/Primitive we can just do:
class MyClass {
static get MY_CONST() {
return 'string';
}
get MY_CONST() {
return this.constructor.MY_CONST;
}
constructor() {
alert(this.MY_CONST === this.constructor.MY_CONST);
}
}
alert(MyClass.MY_CONST);
new MyClass
// alert: string ; true
But if MY_CONST is reference type like static get MY_CONST() {return ['string'];} alert output is string, false. In such case delete operator can do the trick:
class MyClass {
static get MY_CONST() {
delete MyClass.MY_CONST;
return MyClass.MY_CONST = 'string';
}
get MY_CONST() {
return this.constructor.MY_CONST;
}
constructor() {
alert(this.MY_CONST === this.constructor.MY_CONST);
}
}
alert(MyClass.MY_CONST);
new MyClass
// alert: string ; true
And finally for class variable not const:
class MyClass {
static get MY_CONST() {
delete MyClass.MY_CONST;
return MyClass.MY_CONST = 'string';
}
static set U_YIN_YANG(value) {
delete MyClass.MY_CONST;
MyClass.MY_CONST = value;
}
get MY_CONST() {
return this.constructor.MY_CONST;
}
set MY_CONST(value) {
this.constructor.MY_CONST = value;
}
constructor() {
alert(this.MY_CONST === this.constructor.MY_CONST);
}
}
alert(MyClass.MY_CONST);
new MyClass
// alert: string, true
MyClass.MY_CONST = ['string, 42']
alert(MyClass.MY_CONST);
new MyClass
// alert: string, 42 ; true
Since your issue is mostly stylistic (not wanting to fill up the constructor with a bunch of declarations) it can be solved stylistically as well.
The way I view it, many class based languages have the constructor be a function named after the class name itself. Stylistically we could use that that to make an ES6 class that stylistically still makes sense but does not group the typical actions taking place in the constructor with all the property declarations we're doing. We simply use the actual JS constructor as the "declaration area", then make a class named function that we otherwise treat as the "other constructor stuff" area, calling it at the end of the true constructor.
"use strict";
class MyClass
{
// only declare your properties and then call this.ClassName(); from here
constructor(){
this.prop1 = 'blah 1';
this.prop2 = 'blah 2';
this.prop3 = 'blah 3';
this.MyClass();
}
// all sorts of other "constructor" stuff, no longer jumbled with declarations
MyClass() {
doWhatever();
}
}
Both will be called as the new instance is constructed.
Sorta like having 2 constructors where you separate out the declarations and the other constructor actions you want to take, and stylistically makes it not too hard to understand that's what is going on too.
I find it's a nice style to use when dealing with a lot of declarations and/or a lot of actions needing to happen on instantiation and wanting to keep the two ideas distinct from each other.
NOTE: I very purposefully do not use the typical idiomatic ideas of "initializing" (like an init() or initialize() method) because those are often used differently. There is a sort of presumed difference between the idea of constructing and initializing. Working with constructors people know that they're called automatically as part of instantiation. Seeing an init method many people are going to assume without a second glance that they need to be doing something along the form of var mc = MyClass(); mc.init();, because that's how you typically initialize. I'm not trying to add an initialization process for the user of the class, I'm trying to add to the construction process of the class itself.
While some people may do a double-take for a moment, that's actually the bit of the point: it communicates to them that the intent is part of construction, even if that makes them do a bit of a double take and go "that's not how ES6 constructors work" and take a second looking at the actual constructor to go "oh, they call it at the bottom, I see", that's far better than NOT communicating that intent (or incorrectly communicating it) and probably getting a lot of people using it wrong, trying to initialize it from the outside and junk. That's very much intentional to the pattern I suggest.
For those that don't want to follow that pattern, the exact opposite can work too. Farm the declarations out to another function at the beginning. Maybe name it "properties" or "publicProperties" or something. Then put the rest of the stuff in the normal constructor.
"use strict";
class MyClass
{
properties() {
this.prop1 = 'blah 1';
this.prop2 = 'blah 2';
this.prop3 = 'blah 3';
}
constructor() {
this.properties();
doWhatever();
}
}
Note that this second method may look cleaner but it also has an inherent problem where properties gets overridden as one class using this method extends another. You'd have to give more unique names to properties to avoid that. My first method does not have this problem because its fake half of the constructor is uniquely named after the class.
As Benjamin said in his answer, TC39 explicitly decided not to include this feature at least for ES2015. However, the consensus seems to be that they will add it in ES2016.
The syntax hasn't been decided yet, but there's a preliminary proposal for ES2016 that will allow you to declare static properties on a class.
Thanks to the magic of babel, you can use this today. Enable the class properties transform according to these instructions and you're good to go. Here's an example of the syntax:
class foo {
static myProp = 'bar'
someFunction() {
console.log(this.myProp)
}
}
This proposal is in a very early state, so be prepared to tweak your syntax as time goes on.
What about the oldschool way?
class MyClass {
constructor(count){
this.countVar = 1 + count;
}
}
MyClass.prototype.foo = "foo";
MyClass.prototype.countVar = 0;
// ...
var o1 = new MyClass(2); o2 = new MyClass(3);
o1.foo = "newFoo";
console.log( o1.foo,o2.foo);
console.log( o1.countVar,o2.countVar);
In constructor you mention only those vars which have to be computed.
I like prototype inheritance for this feature -- it can help to save a lot of memory(in case if there are a lot of never-assigned vars).
[Long thread, not sure if its already listed as an option...].
A simple alternative for contsants only, would be defining the const outside of class.
This will be accessible only from the module itself, unless accompanied with a getter.
This way prototype isn't littered and you get the const.
// will be accessible only from the module itself
const MY_CONST = 'string';
class MyClass {
// optional, if external access is desired
static get MY_CONST(){return MY_CONST;}
// access example
static someMethod(){
console.log(MY_CONST);
}
}
ES7 class member syntax:
ES7 has a solution for 'junking' your constructor function. Here is an example:
class Car {
wheels = 4;
weight = 100;
}
const car = new Car();
console.log(car.wheels, car.weight);
The above example would look the following in ES6:
class Car {
constructor() {
this.wheels = 4;
this.weight = 100;
}
}
const car = new Car();
console.log(car.wheels, car.weight);
Be aware when using this that this syntax might not be supported by all browsers and might have to be transpiled an earlier version of JS.
Bonus: an object factory:
function generateCar(wheels, weight) {
class Car {
constructor() {}
wheels = wheels;
weight = weight;
}
return new Car();
}
const car1 = generateCar(4, 50);
const car2 = generateCar(6, 100);
console.log(car1.wheels, car1.weight);
console.log(car2.wheels, car2.weight);
You can mimic es6 classes behaviour... and use your class variables :)
Look mum... no classes!
// Helper
const $constructor = Symbol();
const $extends = (parent, child) =>
Object.assign(Object.create(parent), child);
const $new = (object, ...args) => {
let instance = Object.create(object);
instance[$constructor].call(instance, ...args);
return instance;
}
const $super = (parent, context, ...args) => {
parent[$constructor].call(context, ...args)
}
// class
var Foo = {
classVariable: true,
// constructor
[$constructor](who){
this.me = who;
this.species = 'fufel';
},
// methods
identify(){
return 'I am ' + this.me;
}
}
// class extends Foo
var Bar = $extends(Foo, {
// constructor
[$constructor](who){
$super(Foo, this, who);
this.subtype = 'barashek';
},
// methods
speak(){
console.log('Hello, ' + this.identify());
},
bark(num){
console.log('Woof');
}
});
var a1 = $new(Foo, 'a1');
var b1 = $new(Bar, 'b1');
console.log(a1, b1);
console.log('b1.classVariable', b1.classVariable);
I put it on GitHub
Still you can't declare any classes like in another programming languages. But you can create as many class variables. But problem is scope of class object. So According to me, Best way OOP Programming in ES6 Javascript:-
class foo{
constructor(){
//decalre your all variables
this.MY_CONST = 3.14;
this.x = 5;
this.y = 7;
// or call another method to declare more variables outside from constructor.
// now create method level object reference and public level property
this.MySelf = this;
// you can also use var modifier rather than property but that is not working good
let self = this.MySelf;
//code .........
}
set MySelf(v){
this.mySelf = v;
}
get MySelf(v){
return this.mySelf;
}
myMethod(cd){
// now use as object reference it in any method of class
let self = this.MySelf;
// now use self as object reference in code
}
}
If its only the cluttering what gives the problem in the constructor why not implement a initialize method that intializes the variables. This is a normal thing to do when the constructor gets to full with unnecessary stuff. Even in typed program languages like C# its normal convention to add an Initialize method to handle that.
Just define a getter.
class MyClass
{
get MY_CONST () { return 'string'; }
constructor ()
{
console.log ("MyClass MY_CONST:", this.MY_CONST);
}
}
var obj = new MyClass();
The way I solved this, which is another option (if you have jQuery available), was to Define the fields in an old-school object and then extend the class with that object. I also didn't want to pepper the constructor with assignments, this appeared to be a neat solution.
function MyClassFields(){
this.createdAt = new Date();
}
MyClassFields.prototype = {
id : '',
type : '',
title : '',
createdAt : null,
};
class MyClass {
constructor() {
$.extend(this,new MyClassFields());
}
};
-- Update Following Bergi's comment.
No JQuery Version:
class SavedSearch {
constructor() {
Object.assign(this,{
id : '',
type : '',
title : '',
createdAt: new Date(),
});
}
}
You still do end up with 'fat' constructor, but at least its all in one class and assigned in one hit.
EDIT #2:
I've now gone full circle and am now assigning values in the constructor, e.g.
class SavedSearch {
constructor() {
this.id = '';
this.type = '';
this.title = '';
this.createdAt = new Date();
}
}
Why? Simple really, using the above plus some JSdoc comments, PHPStorm was able to perform code completion on the properties. Assigning all the vars in one hit was nice, but the inability to code complete the properties, imo, isn't worth the (almost certainly minuscule) performance benefit.
Well, you can declare variables inside the Constructor.
class Foo {
constructor() {
var name = "foo"
this.method = function() {
return name
}
}
}
var foo = new Foo()
foo.method()
Recent browsers as of 2021 (not IE, see MDN browser chart) implement Public class fields which seems to be what you're looking for:
class MyClass {
static foo = 3;
}
console.log(MyClass.foo);
However apparently it's not possible to make this a const: Declaring static constants in ES6 classes?
A static getter looks pretty close:
class MyClass {
static get CONST() {
return 3;
}
}
MyClass.CONST = 4; // property unaffected
console.log(MyClass.CONST);
This is a bit hackish combo of static and get works for me
class ConstantThingy{
static get NO_REENTER__INIT() {
if(ConstantThingy._NO_REENTER__INIT== null){
ConstantThingy._NO_REENTER__INIT = new ConstantThingy(false,true);
}
return ConstantThingy._NO_REENTER__INIT;
}
}
elsewhere used
var conf = ConstantThingy.NO_REENTER__INIT;
if(conf.init)...

Create an instance of a class in ES6 with a dynamic name? [duplicate]

This question already has answers here:
Create object from class name in JavasScript ECMAScript 6
(8 answers)
Closed 6 years ago.
I want to be able to instantiate a particular ES6 class by passing a string variable to a function. Depending on the value of the variable, a different class will be created.
Example - I have 2 classes, ClassOne, ClassTwo. I want to be able to pass a variable to a function and get a new class back. The name of the class will be related to the variable - eg. passing 'Two' will create ClassTwo.
I don't want to just use a switch clause like this:
function createRelevantClass( desiredSubclassName )
{
let args = [],
newClass;
switch( desiredSubclassName )
{
case 'One' :
newClass = new ClassOne(args);
break;
case 'Two' :
newClass = new ClassTwo(args);
break;
}
return newClass;
}
Instead, I want to somehow be able to create the constructor call using the variable name. Is that possible?
function createRelevantClass( desiredSubclassName )
{
// desiredSubclassName would be string 'One' or 'Two'
// how to use the 'new' operator or Reflect here to create the class based on the variable passed in
let newClass = ( *magic code to build constructor dynamically* );
return newClass;
}
There are a few ways you can accomplish this...
1. Proxy Class
Following from #thefourtheye's example of maintaining a mapping of name to class, you could have a class whose job is to take the name of the desired class and proxy its instantiation:
[ See it working ]
Define your classes
// ClassOne.js
export class ClassOne {
constructor () {
console.log("Hi from ClassOne");
}
}
// ClassTwo.js
export class ClassTwo {
constructor (msg) {
console.log(`${msg} from ClassTwo`);
}
}
Define the proxy class (e.g. DynamicClass)
import ClassOne from './ClassOne';
import ClassTwo from './ClassTwo';
// Use ES6 Object Literal Property Value Shorthand to maintain a map
// where the keys share the same names as the classes themselves
const classes = {
ClassOne,
ClassTwo
};
class DynamicClass {
constructor (className, opts) {
return new classes[className](opts);
}
}
export default DynamicClass;
Example usage
import DynamicClass from './DynamicClass';
new DynamicClass('ClassOne'); //=> "Hi from ClassOne"
new DynamicClass('ClassTwo', 'Bye'); //=> "Bye from ClassTwo"
2. Factory Function
Use a function that performs a lookup against an object of class name -> class mappings and returns reference to the class, which we can then instantiate as usual.
Define the factory function
import ClassOne from './ClassOne';
import ClassTwo from './ClassTwo';
const classes = { ClassOne, ClassTwo };
export default function dynamicClass (name) {
return classes[name];
}
Example usage
import dynamicClass from './dynamicClass'
const ClassOne = dynamicClass('ClassOne') // Get the ClassOne class
new ClassOne(args) // Create an instance of ClassOne
Store the classes in an Object, with the keys being the names of the classes you want them to be.
const classesMapping = {
'One': ClassOne,
'Two': ClassTwo
};
then create the class based on the key name like this
return new classesMapping[desiredSubclassName](args);

Categories