Create class with arguments or call its methods separately - javascript

How JS code should be structered when instantiating new classes inside controller class Main.
Solutions:
A: pass arguments while creating new class - new Options(args) - and let Options's constructor call its own methods.
B: create new class and call the classes' methods on the object.
Later I'd use properties from Options in another classes.
// A
class Main {
constructor(options) {
this.options = new Options(options);
{ firstProperty, secondProperty } = this.options;
this.another = new Another(firstProperty, secondProperty);
}
}
// B
class Main {
constructor(options) {
this.options = new Options();
const firstProperty = this.options.methodA(options);
const secondProperty = this.options.methodB(options);
this.another = new Another();
const anotherPropety = this.another.methodA(firstProperty);
(...)
}
}

For the purposes of decoupling I would suggest a third option.
//main.js
class Main {
constructor(options) {
this.options = options;
// all instances of class would have:
this.options.foo = 'bar'
}
method() {
return `${this.options.foo} - ${this.options.setup}`
}
}
// example.js
const options = new Options({setup: 'options'});
const example = new Main(options);
console.log(example.method());
This lets your inject your dependencies into a given class, which makes writing tests for your code far simpler. It also gives you the benefit of (as long as you maintain a common interface) swapping out Options for NewAwesomeOptions at some later point without having to find everywhere you might have hard coded it into a class.

Related

Is there a Class equivalent to prototype attributes?

If I have the following code:
function Thing() {}
Thing.prototype.cache = {};
var a = new Thing();
var b = new Thing();
a.cache === b.cache // true
How might I re-write this using proper Classes?
I know about public class fields, but those are defined on the instance rather than on the class prototype.
// For Example:
class Thing {
cache = {}
}
var a = new Thing();
var b = new Thing();
a.cache === b.cache // false
There's no class syntax to put non-methods on the prototype, you'll have to slap them on afterwards, like this:
class Thing {}
Thing.prototype.cache = {}
// Example usage //
const thing1 = new Thing()
const thing2 = new Thing()
thing1.cache.x = 2
console.log(thing2.cache.x)
However, putting mutable data on the prototype is seen by many as magical and difficult to follow. Here's another way to achieve a very similar effect.
// This can alternativly be a static, private variable on the class.
const thingCache = {}
class Thing {
cache = thingCache
}
// Example usage //
const thing1 = new Thing()
const thing2 = new Thing()
thing1.cache.x = 2
console.log(thing2.cache.x)
But, what I would really recommend is to just make this property a static property on the class - that's what static properties are for, sharing a single piece of data between many instances.
class Thing {
static cache = {}
}
// Example usage //
Thing.cache.x = 2
console.log(Thing.cache.x)
// In some rare scenarios where you only have the instance,
// and not the original class, you can still retrieve the original class
const thing1 = new Thing()
const thing2 = new Thing()
thing1.constructor.cache.x = 2
console.log(thing2.constructor.cache.x)
What you have implemented is called class field, as you noticed in your case it's an equivalent of:
class Thing {
constructor() {
this.cache = {};
}
}
You have no other choice than attaching it to the prototype just like you'd do in ES5, because everything you declare inside a class is either:
A class field (attached to the instance)
A method (attached to the prototype)
A static field (attached to the class)
class Thing {
}
Thing.prototype.cache = {};
const obj1 = new Thing;
const obj2 = new Thing;
console.log(obj1.cache === obj2.cache);
I have posted a similar question here: Not able to update a class property in ES6
As a side note, as you're not using a constructor, the class is needless so you could write this:
const proto = { cache: {} };
const obj1 = Object.create(proto);
const obj2 = Object.create(proto);
console.log(obj1.cache === obj2.cache);
You want to create a static class field
Then define getters and setters based off the class name.
Public static fields are useful when you want a field to exist only once per class, not on every class instance you create. This is useful for caches, fixed-configuration, or any other data you don't need to be replicated across instances.
Public static fields are declared using the static keyword. They are added to the class constructor at the time of class evaluation using Object.defineProperty(). They are accessed again from the class constructor.
Fields without initializers are initialized to undefined.
Public static fields are not reinitialized on subclasses, but can be accessed via the prototype chain.
When initializing fields, this refers to the class constructor. You can also reference it by name, and use super to get the superclass constructor (if one exists).
class Thing {
static _cache = {};
get cache() { return Thing._cache; }
set cache(newValue) { Thing._cache = newValue; }
}
var a = new Thing();
var b = new Thing();
b.cache.key = "value";
console.log(a.cache === b.cache);
console.log(a.cache)
As Scotty Jamison said, the class syntax still does not provide a way to create fields on the prototype itself, so you'll have to do it with [class].prototype.field. However, if you wish to do so within the class declaration itself, you can use static blocks:
class Thing {
static {
this.prototype.boofar = {};
}
}
Code inside a static block will run when the class is initialized, with this bound to the class itself. This is arguably more confusing than just assigning the value after the class declaration, as using static might suggest a static property, not an inherited one. Still, it's nice for the class to not depend on external code.

How to instantiate a Class from a String in JavaScript

I'm in a weird situation that i need to instantiate a new Class with a string stored in a variable but even i'm sure the class name is correct i get an error that given class name is not a constructor
Here is a dummy code that doesn't work:
class Foo {
constructor(){
console.log('Foo!');
}
};
const foo = 'Foo';
const bar = new window[foo]();
console.log(bar);
This trow this error:
Uncaught TypeError: window[foo] is not a constructor
One possibility is to use eval.
class Foo {
constructor() {
console.log('Foo!');
}
};
const foo = 'Foo';
const bar = eval(`new ${foo}()`);
console.log(bar);
You will have to evaluate the safety of using eval() in your particular circumstances. If you know the origin of the string you are inserting into the code that you run eval() on or you can sanitize it first, then it may be safe.
I personally would prefer a lookup table. If you have a known number of classes that you want to map by string, then you can make your own lookup table and use that. This has the advantage of there can be no unintended consequences if the string has weird stuff in it:
class Foo {
constructor() {
console.log('Foo!');
}
};
class Goo {
constructor() {
console.log('Goo!');
}
};
// construct dict object that contains our mapping between strings and classes
const dict = new Map([
['Foo', Foo],
['Goo', Goo]
]);
// make a class from a string
const foo = 'Foo';
let bar = new(dict.get(foo))()
console.log(bar);
If you were really going to go this route, you may want to encapsulate it in a function and then add error handling if the string is not found in the dict.
This should be better than using the global or Window object as your lookup mechanism for a couple reasons:
If I recall, class definitions in ES6 are not automatically put on the global object like they would with other top level variable declarations (Javascript trying to avoid adding more junk on top of prior design mistakes).
So, if you're going to manually assign to a lookup object, you might as well use a different object and not pollute the global object. That's what the dict object is used for here.
Similar to #jfriend00 ...
const className = "Foo";
const dynamicConstructor = {};
dynamicConstructor[className] = class {
constructor() {
console.log('Foo!');
}
};
const fooInstance = new dynamicConstructor[className]();
console.log(fooInstance);
A sort of factory class constructor can also be used
const classFactory = (_className) => {
let dynamicConstructor = {};
dynamicConstructor[_className] = class {
constructor(_code) {
this.code = _code;
console.log(`${_className} initialised with code: ${_code}!`);
}
};
return dynamicConstructor[_className];
}
const MyClass = classFactory("Foo");
let fooInstance2 = new MyClass(123);
console.debug(fooInstance2);
There are good solutions but I think we need a little bit more of theory
This questions is the best example to use a Factory Pattern
Design patterns can make your code more flexible, more resilient to change and easier to maintain.
Teo, is possible to use Factory Patterns in JavaScript?
Yes, my young Padawan.
But, what is the Factory pattern?
The factory pattern is a creational design pattern, which means it deals with object creation. There are three theoretical types of factory patterns:
Simple factory
Factory method
Abstract factory
Simple Factory is an object which encapsulates the creation of another object. In ES6 it could be a constructor being instantiated by new in a function and then return the instance like this:
class Player {...}
const PlayerFactory = {
makePlayer: (type, level) => new Player(type, level),
}
In this example makePlayer returns the instance of the Player class.
Factory Method defines one method for creating a class instance, which is overridden by subclasses who decide what to return.
In ES6 it could be implemented extending classes because there are no interfaces and using a method to instantiate and object using new
class Dragon {...}
class Snake {...}
class Player {
fightMonster() {
const monster = this.makeMonster()
monster.attack()
}
}
class Warrior extends Player {
makeMonster() {
return new Dragon()
}
}
class Knight extends Player {
makeMonster() {
return new Snake()
}
}
In this example, Player class call makeMonster method then Warrior and Knight classes override makeMoster method to return either a Dragon or a Snake class instance.
Finally, the Abstract Factory provides an interface for creating families of related or dependent objects without specifying their concrete classes
The interpretation of families could be like a category or list of classes. In ES6 it can be an instance that encapsulates a group of individual factories with a common goal. It separates the details of implementation of a set of objects from their general usage.
class WinButton {
constructor(options) {
this.name = 'WinButton'
this.options = options
}
}
class LinuxButton {
constructor(v) {
this.name = 'LinuxButton'
this.options = options
}
}
// Mapping names and class definitions
const supportedOS = new Map([
['Windows', WinButton],
['Linux', LinuxButton]
])
// Use a Factory Abstract pattern
const classFactory = (c, v) => {
const maker = supportedOS.get(c)
return new(maker)(v)
}
// Factory a class from a string
const name = 'Windows'
const param = {enabled: true}
const btn = classFactory(name, param)
console.log({btn})
In this final example we can see that the function classFactory uses the Factory Abstract pattern because instantiate a class from a list of supported OS (Linux or Windows) by setting maker with the constructor of the desired class, in this case WinButton class.

How to define a public property in the javascript class?

With ES5 constructor and prototype approach I can add public (prototype) properties as below:
function Utils(){}
Utils.prototype.data = {};
var utils = new Utils();
console.log(utils.data); //{}
The ES6 class allows me to define only public methods in the class. I build an app with a class-approach and I don't want to mix constructors and classes features. The working code that I figured out is:
class Utils(){
get _data(){
const proto = Object.getPrototypeOf(this);
if(!proto._status) proto._data = {};
return proto._data;
}
}
const utils = new Utils();
console.log(utils._data); //{}
When I call _data getter method, it checkes whether the _data property exists in the prototype object. If so, it returns it, otherwise it initiates the _data property.
Is it a good practice? Is there any other way to do it better?
To make data a public instance property:
class Utils {
constructor () {
this.data = {}
}
}
To make data a public static property, get/set is probably the best way:
let data = {}
class Utils {
get _data () {
return data
}
set _data (d) {
data = d
}
}
I don't know if the code you provided is your full code or not, but when I run it, it throws an error:
class Utils {
get _data(){
const proto = Object.getPrototypeOf(this);
if(!proto._status) proto._data = {};
return proto._data;
}
}
/* TEST */
const a = new Utils();
a._data.ok = 'ok';
const b = new Utils();
console.log(b._data.ok);
If I understand you correctly, you want all instances of Utils to share the same data property.
There is a few ways that I can think of to do what you need, but it might "mix constructors and classes features" (I don't really get what you mean by that).
1: Good ol' ES5 way
class Utils {}
Utils.prototype.data = {};
/* TEST */
const a = new Utils();
a.data.ok = 'ok';
const b = new Utils();
console.log(b.data.ok);
2: Same as your way, but in it's constructor
class Utils {
constructor(){
if (!this.data) {
Utils.prototype.data = {};
}
}
}
/* TEST */
const a = new Utils();
a.data.ok = 'ok';
const b = new Utils();
console.log(b.data.ok);
Though, as the data property needs to be shared across instances, I'd suggest you to add the property to its prototype using Object.defineProperty method to make it unwritable and unconfigurable:
Object.defineProperty(Utils.prototype, 'data', {
value: {},
writable: false,
enumerable: true,
configurable: false
});
This is to ensure the data property cannot be reassigned or deleted, thus minimising the chance of mistakenly reset the data or etc.
I'd recommend the first way (with Object.defineProperty) because it is :
More foolproof
Clearer
Easier to maintain

How to access parent methods from submodule?

I have the following module/class and submodule setup
MyAPI.js
class MyAPI {
construction(){
this.food = require('./Food');
}
}
module.exports = MyAPI;
Food.js
class Food {
constructor(){
...
}
}
module.exports = Food;
app.js
var api = require('./MyAPI');
var taco = new api.food;
var cheeseburger = new api.food;
What I'm wondering, is it possible to call upon MyAPI properties and functions form within Food.js? Do I need to pass this into the require somehow?
this.food = require('./Food')(this); // this didn't work...
The above resulted in this:
TypeError: Class constructors cannot be invoked without 'new'
But why would I use new in the MyAPI constructor?
What is the best approach here to do subclasses and submodules and creating new objects from them?
I think you are confusing classes and instances:
var MyAPI = require('./MyAPI');//this is a class
var apiInstance = new MyAPI();//the creates a new instance of your class
var taco = new apiInstance.food //the food property on your api is a class not an instance
var tacoInstance = new taco();
this.food is assigned in the constructor of MyApi, so you will need to instantiate MyApi to have the property accessible.
var Api = require('./MyAPI');
var apiInstance = new Api();
var foodInstance = new apiInstance.food();
From your comment, it seems like you want properties of MyApi, particularly config to be accessible by submodules. I don't see a way of doing this except to make your top level API object a singleton:
var MyAPI = {
config: { setting: 'default' },
Food: require('./Food')
}
module.exports = MyAPI;
var MyApi = require('./my-api.js');
class Food {
constructor(){
// MyApi.config
}
}
module.exports = Food;
Looking at the AWS source they are doing something similar (except config is it's own module mounted on the top level AWS object).

How to create new instance of parent class in javascript

I have a class that inherits from another. Within the base class, I'm wondering if it is possible to create and return a new instance of the calling parent class.
Here's an example:
Base:
var util = require('util');
var ThingBase = function(options) {
}
ThingBase.prototype.mapper = function(data) {
// do a bunch of stuff with data and then
// return new instance of parent class
};
Parent:
var FooThing = function(options) {
ThingBase.call(this, options);
};
util.inherits(FooThing, ThingBase);
FooThing.someMethod = function() {
var data = 'some data';
var newFooThing = this.mapper(data); // should return new FooThing instance
};
The reason why I wouldn't just create a new instance from someMethod is that I want mapper to do a bunch of stuff to the data before it returns an instance. The stuff it will need to do is the same for all classes that inherit from Base. I don't want to clutter up my classes with boilerplate to create a new instance of itself.
Is this possible? How might I go about achieving something like this?
Assuming that FooThing.prototype.constructor == FooThing, you could use
ThingBase.prototype.mapper = function(data) {
return new this.constructor(crunch(data));
};
All other solutions would "clutter up [your] classes with boilerplate [code]", yes, however they don't rely on a correctly set constructor property.

Categories