Possible to make Class/Instance-wide getters & setters in JS? - javascript

I am trying to implement a singleton class in JS with a ton of getters and setters. It would be much more convenient if I could define a prototype/parent to all of the getters & setters in this class, as they will all need to try and initialize the singleton before returning a value
export class GameData {
constructor() {
this._level = 0;
this._score = 0;
}
static _init() {
if (!GameData._inst) {
GameData._inst = new GameData();
}
}
static clear() {
GameData._inst = null;
}
/*
Properties
I do not want to call GameData.init() on every property, any way to only do this once?
*/
static get level() {
GameData._init();
return GameData._inst._level;
}
static set level(val) {
GameData._init();
GameData._inst._level = val;
}
static get score() {
GameData._init();
return GameData._inst._score;
}
static set score(val) {
GameData._init();
GameData._inst._score = val
}

Yep, stop making singletons. And getters. And setters.
class GameData {
constructor() {
this.level = 0;
this.score = 0;
}
}
export default new GameData();

Related

JavaScript: How to call STATIC method on superclass but the method isnot defined on the derivated class?

I have these classes:
class Control {
static Code = 3;
static GetCodeChain() {
var result = [];
result.push(this.Code);
if (super.GetCodeChain) {
result = result.concat(super.GetCodeChain());
}
return result;
}
}
class SubControl extends Control {
static Code = 2;
}
class AnotherControl extends SubControl {
static Code = 1;
}
console.log(AnotherControl.GetCodeChain()); // prints 1, but I want [1,2,3]
I cannot write the methods on the subclasses.
How to call the STATIC method on the superclass properly?
I think I found a solution:
class Control {
static Code = 3;
static GetCodeChain() {
var result = [];
for (var proto = this; proto; proto = Object.getPrototypeOf(proto)) {
if (proto.Code) { result.push(proto.Code);}
}
return result;
}
}
class SubControl extends Control {
static Code = 2;
}
class AnotherControl extends SubControl {
static Code = 1;
}
console.log(AnotherControl.GetCodeChain()); //prints [1,2,3]
Using "Object.getPrototypeOf(this)" I can access the superclass even on an static context. On this context, "this" is the derivated class itself. No instances are involved on this solution.

Referencing this or instance in javascript singleton

I was dabbling with singletons and extending classes and came up with this 'working' code.
let instance = null;
class Motorcycle {
constructor(engine, color) {
if(!instance) {
this.engine = engine;
this.color = color;
instance = this;
} else {
return instance;
}
}
}
class Car extends Motorcycle {
constructor(engine, color) {
if(!instance) {
super(engine, color);
this.doors = 4;
} else {
return instance;
}
}
}
class SUV extends Car {
constructor(engine, color, doors) {
if(!instance) {
super(engine,color);
instance.doors = doors == undefined ? 5 : doors;
} else {
return instance;
}
}
}
I realized that in the constructor for sub-classes I can set doors with instance.doors and this.doors.
So I have two questions:
Does it matter enough to have a best practice?
What is that best practice?
Does it matter enough to have a best practice?
Not in your case probably, but there is a difference between them.
What is that best practice?
Do not use singletons. Especially not when subclassing. Your code is unable to create an instance of a separate subclass, is unable to create multiple cars, is unable to create a new SUV after creating a new Car (and also a Car is not a MotorCycle).
Your singleton objects should exist outside the constructor; preferably inside a map and accessed by their class name. Constructors should really only call their super constructor or set instance variables.
You will also need a way to dynamically bind constructor arguments, this is taken care of in the ctorBind function below.
You could also call getSingleton(SUV) a second time without args. This example just shows that the original SUV was not modified upon returning.
// REFLECTION UTILS
const getClassName = (obj) => obj.toString().match(/ (\w+)/)[1]
const ctorBind = (ctor, args) => new (ctor.bind.apply(ctor, [null].concat(args)))()
// MAIN FUNCTION
const main = () => {
console.log(getSingleton(SUV, 'V6', 'Red', 5))
console.log(getSingleton(SUV, 'V8', 'Blue', 6)) // Not updated...
}
// BEGIN SINGLETON MAP AND LOOKUP
const vehicleSingletons = {};
function getSingleton(vehicleClass, ...args) {
const className = getClassName(vehicleClass)
if (vehicleSingletons[className] == null) {
vehicleSingletons[className] = ctorBind(vehicleClass, args);
}
return vehicleSingletons[className];
}
// BEGIN CLASS DEFINITIONS
class Vehicle {
constructor(engine, color) {
this.engine = engine;
this.color = color;
}
}
class Motorcycle extends Vehicle {
constructor(engine, color) {
super(engine, color);
}
}
class Car extends Vehicle {
constructor(engine, color, doors) {
super(engine, color);
this.doors = doors;
}
}
class SUV extends Car {
constructor(engine, color, doors) {
super(engine, color, doors);
}
}
main()
.as-console-wrapper { top: 0; max-height: 100% !important; }

Automatically adding inheritance as properties on constructor, javascript

I'm moving a (heavy) file-parser to a webworker. Webworkers can only return serialized json. Hence, I cant return function that gives me object that inherit from a specific class:
getCollection: function getCollection(cls) {
if (!cls) {
return this.myObjects;
}
let collection = cls.toUpperCase();
let arr = [];
for (let i = 0; i < this.nrOfObjects; i++) {
if(classes[collection]){
if (this.myObjects[i] instanceof classes[collection]) {
arr.push(this.myObjects[i]);
}
}
}
return arr;
}
So for example getCollection('product') would return all instances that inherits from the class product
When I move the parsers to the webworker, I can't return this function. Is there anyway I can add the inheritance to my constructor in my classes instead?
export class Column extends BuildingElment {
constructor(GlobalId, OwnerHistory, Name, Description) {
super(GlobalId, OwnerHistory, Name);
this.description = Description
this.nameOfClass = 'Column';
this.inheritance = ['Root', 'Product', 'BuildingElement'] // Can this be somehow automatically generated??
}
}
Or if there is any other way I can work with webworkers and still get the inheritance.

Default value of Type in Generic classes in Typescript

I need to set a default value of a variable based on its type in Typescript generic classes like below
class MyClass<T>{
myvariable: T // Here I want to set the value of this variable
// with the default value of the type passed in 'T'
}
For example if the T is number then the default value of the variable myvariable should be "0", similarly for string it should be empty string and so on..
You can't do that as the actual type which is T will only be known at runtime.
What you can do:
abstract class MyClass<T> {
myvariable: T;
constructor() {
this.myvariable = this.getInitialValue();
}
protected abstract getInitialValue(): T;
}
Now you just extend this class, like so:
class MyStringClass extends MyClass<string> {
protected getInitialValue(): string {
return "init-value";
}
}
Edit
What you're asking for can not be done because T only exists in the typescript realm, and it doesn't "survive" the compilation process.
For example, this:
class MyClass<T> {
myvariable: T;
constructor(value: T) {
this.myvariable = value;
}
}
Compiles into:
var MyClass = (function () {
function MyClass(value) {
this.myvariable = value;
}
return MyClass;
}());
As you can see, in the compiled version there's no T, so you can't use that information at runtime in order to generate a default value.
Another solution is to have a map of default values:
var defaultValues = {
"string": "",
"number": 0,
"boolean": false
}
class MyClass<T> {
myvariable: T;
constructor(value: T) {
this.myvariable = value;
}
}
let a = new MyClass<string>(defaultValues.string);
let b = new MyClass<boolean>(defaultValues.boolean);
You can also use static factory methods:
class MyClass<T> {
myvariable: T;
constructor(value: T) {
this.myvariable = value;
}
static stringInstance(): MyClass<string> {
return new MyClass<string>("");
}
static numberInstance(): MyClass<number> {
return new MyClass<number>(0);
}
static booleanInstance(): MyClass<boolean> {
return new MyClass<boolean>(false);
}
}
let a = MyClass.stringInstance();
let b = MyClass.booleanInstance();
T is lost at runtime, so you need to pass in the type like a value. And you can do that by passing the constructor.
When I have a similar need, I usually use an interface like this:
interface Type<T> {
new() : T;
}
And create MyClass like so:
class MyClass<T>{
myvariable: T;
constructor(type: Type<T>) {
this.myvariable = new type();
}
}
Then I can use MyClass like so:
let myinstance = new MyClass(TheOtherType);
It works for classes, but not for built-ins like string and number.
TheOtherType is the constructor of a class such as:
class TheOtherType {
}
Well I guess I came up with a possible solution. Anyway I must say that Nitzan Tomer is right and the class type is not available in runtime. This is the funny side of TS :)
Here you can check the type of the objects you get in and then set a default value. You could also change the place and the objects to check in order to do it the way you want but I guess it could be a good starting point for you. Notice that you have to do a double cast because TS cannot guarantee that types are compatible. I haven't tried it yet but it compiles well
class MyClass<T>{
myvariable: T
constructor(param: any) {
if (typeof param === 'string') {
this.myvariable = <T><any> "";
}
else if (typeof param === 'number') {
this.myvariable = <T><any> 0;
}
}
}

Declaring static constants in ES6 classes?

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
})

Categories

Resources