object building not working as expected - javascript

I have 3 different classes A, B, C.
A is, essentially, the parent of both B and C. When building both B and C object, A has to be included too.
Eventually, the class returns an object, which is currently B (inherits A), or C (inherits A), or just A.
Initialising of new object:
const A = new A("a", "b", "c").json(); or const C = new C("a", "b", "c", "cClass1", "cClass2").json();
At the moment, I use inheritance to achieve this:
export class A {
constructor (option1, option2, option3) {
this.option1 = option1;
this.option2 = option2;
this.option3 = option3;
}
json () {
return {
key1: this.option1,
key2: this.option2,
key3: this.option3,
};
}
}
export class B extends A {
constructor (option1, option2, option3, bClass1, bClass2) {
super(option1, option2, option3);
this.bClassKey1 = bClass1;
this.bClassKey2 = bClass2;
}
json () {
return {
...super.json(),
bClassKey1: this.bClass1,
bClassKey2: this.bClass2
};
}
}
export class C extends A {
constructor (option1, option2, option3, cClass1, cClass2) {
super(option1, option2, option3);
this.cClassKey1 = cClass1;
this.cClassKey2 = cClass2;
}
json () {
return {
...super.json(),
cClassKey1: this.cClass1,
cClassKey2: this.cClass2
};
}
}
I now need to change how the objects are built, because I need to achieve the following:
I need an object that contains all of the classes unique parameters, like so:
{
key1: option1,
key2: option2,
key3: option3,
bClassKey1: bClass1,
bClassKey2: bClass2,
cClassKey1: cClass1,
cClassKey2: cClass2
}
However, I cannot use multiple inheritance in JS (apart from mixin NPM, but I'd rather attempt to achieve it natively).
How can I return a object, that's built with A parameters, B parameters (without A) and C parameters (without A). However, there's still situations where B and C need to be built, that extends the parent of A.

It sounds like you want to use aggregation rather than inheritance. Then, each of the classes would have a method that added its information to an object, and you'd use any combination of those methods you wanted.
/*export*/ class A {
constructor (option1, option2, option3) {
this.option1 = option1;
this.option2 = option2;
this.option3 = option3;
}
json (target = {}) {
// Could also use Object.assign here if **all** enumerable properties are desired
target.key1 = this.option1;
target.key2 = this.option2;
target.key3 = this.option3;
return target;
}
}
/*export*/ class B {
constructor (bClass1, bClass2) {
this.bClassKey1 = bClass1;
this.bClassKey2 = bClass2;
}
json (target = {}) {
// Could also use Object.assign here if **all** enumerable properties are desired
// (Duplicating the names introduces the opportunity of error, which in fact
// there was in the question)
target.bClassKey1 = this.bClassKey1;
target.bClassKey2 = this.bClassKey2;
return target;
}
}
/*export*/ class C {
constructor (cClass1, cClass2) {
this.cClassKey1 = cClass1;
this.cClassKey2 = cClass2;
}
json (target = {}) {
// Could also use Object.assign here if **all** enumerable properties are desired
// (Duplicating the names introduces the opportunity of error, which in fact
// there was in the question)
target.cClassKey1 = this.cClassKey1;
target.cClassKey2 = this.cClassKey2;
return target;
}
}
const a = new A("option1value", "option2value", "option3value");
const b = new B("bClass1value", "bClass2value");
const c = new C("cClass1value", "cClass2value");
const o = c.json(b.json(a.json()));
/* Or, but it requires more temporary objects:
const o = {...a.json(), ...b.json(), ...c.json()};
*/
console.log(o);

Related

How to override spread operation on a javascript class

I have a Class called Myclass
class Myclass {
constructor(
public title: string,
) { }
}
in the next example, I want to change the result of the spread operation
let myobject = new Myclass('hello');
console.log({...myobject});
result wanted for example
{
new_title_name : 'hello'
}
This cannot be done. The ECMA-262 specification describes only one way a spread operator can work with objects, with no ability to override it.
If you want to change the set of key-value pairs spread out, you need to provide a different object. Such an object can be generated by a function, method or a property:
class Myclass {
get data() {
const result = {};
for (const k in this) {
if (typeof this[k] === 'number')
result[k.toUpperCase()] = this[k];
}
return result;
}
};
const obj = new Myclass();
obj.a = [];
obj.b = null;
obj.c = 13;
obj.d = 'test';
console.info({ ...obj.data });

How to use instanceof with a class which is not defined in the current context?

This is going to be a bit tricky but I'll do my best to explain,
Consider the following code:
class A { a() { return true; } }
class B { b() { return new A(); } }
var b = new B();
console.log(b instanceof B); // true
console.log(b.b() instanceof A); // true <--- [1]
It's pretty straightforward to see that (class B).b() is going to return an instance of an object of type(/class) A. And we can evaluate this using the instanceof operator [1].
Now, a problem arises when, for whatever reason, we do not have a definition for class A in our current scope. One scenario where such thing may happen, is when you import/require an object from a library and many of its internal classes are not exposed.
Since there is no definition for A, it is not possible to do <symbol> instanceof A ...
So, how may one actually perform this check under such scenario?
PS: I already tried the Object.prototype.toString... trick to not avail.
You could create a function that traverses the prototype chain and returns a list of all the super classes of an object including the class from which the object was instantiated from.
class Foo {}
class X extends Foo{}
class A extends X{}
class B { b() { return new A(); } }
function getParents(obj) {
const arr = [];
while (obj = Reflect.getPrototypeOf(obj)) {
arr.push(obj.constructor.name);
}
return arr;
}
var b = new B().b();
const parents = getParents(b);
console.log(`b instance of A = ${parents.includes('A')}`);
console.log(`b instance of X = ${parents.includes('X')}`);
console.log(`b instance of Foo = ${parents.includes('Foo')}`);
console.log(`b instance of Object = ${parents.includes('Object')}`);
You could also do this using a recursive function
class Foo {}
class X extends Foo{}
class A extends X{}
class B { b() { return new A(); } }
function getParents(obj, arr = null) {
if (!arr) arr = [];
const protoTypeObj = Reflect.getPrototypeOf(obj);
if (!protoTypeObj) return;
arr.push(protoTypeObj.constructor.name);
getParents(protoTypeObj, arr);
return arr;
}
var b = new B().b();
const parents = getParents(b);
console.log(`b instance of A = ${parents.includes('A')}`);
console.log(`b instance of X = ${parents.includes('X')}`);
console.log(`b instance of Foo = ${parents.includes('Foo')}`);
console.log(`b instance of Object = ${parents.includes('Object')}`);

JavaScript method to over-ride default behaviour of object destructuring

Is there a way in JS to over-ride the default behavior of an object when it is de-structured?
// Normally destructing lifts properties from an object
const foo = {
a: 1,
b: 2,
};
const { a, b } = foo; // a = 1, b = 2
// I would like to have a method return the properties to be
// destructured
const bar = {
toObject: () => {
return { a, b };
},
};
const { a, b } = bar; // a = undefiner, b = undefined
I know that I could simply use const { a, b } = bar.toObject(); but that requires the consumer of the object to know how it's internals work and breaks the principle of least astonishment.
The closest thing I can think of to what I want is the toJSON magic method.
Nope. The specification requires the right hand side to resolve to a value that can be converted to an object via ToObject, which simply returns the object itself if it is passed one (i.e. no special method on the object is called to convert it to something else).
If you'd use array destructuring, that would work:
const [a, b] = {
*[Symbol.iterator]() {
yield "some"; yield "stuff";
}
};
You can make your toObject work as intended by decorating the target with a Proxy that intercepts ownKeys and get to fake an object for destructuring:
let withToObject = obj => new Proxy(obj, {
ownKeys(o) {
return Object.keys(o.toObject())
},
get(o, prop) {
return o.toObject()[prop]
}
});
let bar = withToObject({
aa: 11,
bb: 22,
cc: 33,
toObject() {
return {
a: this.aa,
b: this.bb
};
}
});
const {a, b} = bar;
console.log(a, b)
Of course, this affects not only destructuring, but also any other interaction with the object, like serialization, so you have to take measures to make these work too. For example, to support JSON, patch get like this:
get(o, prop) {
if (prop === 'toJSON')
return () => o; // or o.toObject(), whatever fits better
return o.toObject()[prop]

Passing object with predefined props to class constructor es6

I am trying to pass to my class constructor an object with predefined properties.
like this
class Test {
constructor({
a = "test a",
b = "test b"
}) {
}
}
P.S. I know how to define properties of objects. I want to know how to predefine properties.
It seems you want to pass one object to the constructor and have its properties assigned to single keys of the class. You can use destructuring for that this way:
class MyClass {
constructor({a,b} = {a:1,b:2}) {
this.a = a;
this.b = b;
}
}
Be aware that this is not safe for partially filled objects:
var instance = new MyClass({a:3});
// instance.b == undefined
Handling this could be done like so:
class MyClass {
constructor({a=1,b=2} = {}) {
this.a = a;
this.b = b;
}
}
Which results in:
var instance = new MyClass({a:3});
// instance.a == 3
// instance.b == 2
The simple solution would be to pass the constructor and object during instantiation:
class Test {
constructor(obj){
this.a = obj.a;
this.b = obj.b;
}
};
const obj = {
a: 'value',
b: 'value'
};
const newtTest = new Test(obj);
Like I said in my comment, there's an example right at the top of the documentation.
class Test {
constructor(/* put arguments here if you want to pass any */){
//Pre-define a and b for any new instance of class Test
this.a = "test a";
this.b = "test b";
}
}

Why doesn't JavaScript ES6 support multi-constructor classes?

I want to write my Javascript class like below.
class Option {
constructor() {
this.autoLoad = false;
}
constructor(key, value) {
this[key] = value;
}
constructor(key, value, autoLoad) {
this[key] = value;
this.autoLoad = autoLoad || false;
}
}
I think it would be nice if we can write out class in this way.
Expect to happen:
var option1 = new Option(); // option1 = {autoLoad: false}
var option2 = new Option('foo', 'bar',); // option2 = {foo: 'bar'}
var option3 = new Option('foo', 'bar', false); // option3 = {foo: 'bar', autoLoad: false}
I want to write my Javascript class like below
You can't, in the same way you can't overload standard functions like that. What you can do is use the arguments object to query the number of arguments passed:
class Option {
constructor(key, value, autoLoad) {
// new Option()
if(!arguments.length) {
this.autoLoad = false;
}
// new Option(a, [b, [c]])
else {
this[key] = value;
this.autoLoad = autoLoad || false;
}
}
}
Babel REPL Example
Of course (with your updated example), you could take the approach that you don't care about the number of arguments, rather whether each individual value was passed, in which case you could so something like:
class Option {
constructor(key, value, autoLoad) {
if(!key) { // Could change this to a strict undefined check
this.autoLoad = false;
return;
}
this[key] = value;
this.autoLoad = autoLoad || false;
}
}
What you want is called constructor overloading. This, and the more general case of function overloading, is not supported in ECMAScript.
ECMAScript does not handle missing arguments in the same way as more strict languages. The value of missing arguments is left as undefined instead of raising a error. In this paradigm, it is difficult/impossible to detect which overloaded function you are aiming for.
The idiomatic solution is to have one function and have it handle all the combinations of arguments that you need. For the original example, you can just test for the presence of key and value like this:
class Option {
constructor(key, value, autoLoad = false) {
if (typeof key !== 'undefined') {
this[key] = value;
}
this.autoLoad = autoLoad;
}
}
Another option would be to allow your constructor to take an object that is bound to your class properties:
class Option {
// Assign default values in the constructor object
constructor({key = 'foo', value, autoLoad = true} = {}) {
this.key = key;
// Or on the property with default (not recommended)
this.value = value || 'bar';
this.autoLoad = autoLoad;
console.log('Result:', this);
}
}
var option1 = new Option();
// Logs: {key: "foo", value: "bar", autoLoad: true}
var option2 = new Option({value: 'hello'});
// Logs: {key: "foo", value: "hello", autoLoad: true}
This is even more useful with Typescript as you can ensure type safety with the values passed in (i.e. key could only be a string, autoLoad a boolean etc).
Guessing from your sample code, all you need is to use default values for your parameters:
class Option {
constructor(key = 'foo', value = 'bar', autoLoad = false) {
this[key] = value;
this.autoLoad = autoLoad;
}
}
Having said that, another alternative to constructor overloading is to use static factories. Suppose you would like to be able to instantiate an object from plain parameters, from a hash containing those same parameters or even from a JSON string:
class Thing {
constructor(a, b) {
this.a = a;
this.b = b;
}
static fromHash(hash) {
return new this(hash.a, hash.b);
}
static fromJson(string) {
return this.fromHash(JSON.parse(string));
}
}
let thing = new Thing(1, 2);
// ...
thing = Thing.fromHash({a: 1, b: 2});
// ...
thing = Thing.fromJson('{"a": 1, "b": 2}');
Here's a hack for overloading based on arity (number of arguments). The idea is to create a function from a number of functions with different arities (determined by looking at fn.length).
function overloaded(...inputs) {
var fns = [];
inputs.forEach(f => fns[f.length] = f);
return function() {
return fns[arguments.length].apply(this, arguments);
};
}
var F = overloaded(
function(a) { console.log("function with one argument"); },
function(a, b) { console.log("function with two arguments"); }
);
F(1);
F(2, 3);
Of course this needs a lot of bullet-proofing and cleaning up, but you get the idea. However, I don't think you'll have much luck applying this to ES6 class constructors, because they are a horse of a different color.
you can use static methods,look at my answer to same question
class MyClass {
constructor(a,b,c,d){
this.a = a
this.b = b
this.c = c
this.d = d
}
static BAndCInstance(b,c){
return new MyClass(null,b,c)
}
}
//a Instance that has b and c params
MyClass.BAndCInstance(b,c)
Use object.assigne with arguments with this
This={...this,...arguments}
Its not the overload I wanted, but this is a basic version of how I faked my way through creating an obj1 with some different initialization behavior. I realize I could have expanded the arguments as stated above, but I already had a nasty set of arguments and relatively different data sources to deconstruct that would have really distorted my objectives; this just made it cleaner for my situation...
class obj1{
constructor(v1, v2){
this.a = v1;
this.b = v2;
}
}
class obj1Alt{
constructor(v1, v2){
return new obj1(v1*2,v2*2);
}
}
new obj1(2,4) // returns an obj1
new obj1Alt(2,4) // also returns an obj1
Disclaimer: I've been programming for a long time, but I am fairly new to JS; probably not a best practice.

Categories