I'm trying to construct an instance of a class using the output from a MongoDB database. My problems lies in that my class has nested classes, which I would also like to instantiate. Using Object.assign() seems to only create a top level object, while the inner properties are still 'object', so I don't have access to their methods. For example
let obj = {address: { street: '1234 Rainbow Road' }};
class Person {
constructor(obj) {
Object.assign(this, obj);
}
}
class Address {
constructor(addr) {
this.address = addr;
}
printAddress() {
console.log(this.address);
}
}
let p = new Person(obj);
p.address.printAddress() // fails, no function printAddress
compared to...
class Person {
constructor(obj) {
this.address = new Address(obj.address);
}
}
class Address {
constructor(addr) {
this.address = addr;
}
printAddress() {
console.log(this.address);
}
}
let p = new Person(obj);
p.address.printAddress() // works
This is just an example, my class is quite a bit larger, is there a way to shorthand instantiate all inner classes as well? Or would I have to decompose it like I did in the second code snippet? Thanks!
You can call new Address in the argument list of new Person
let p = new Person(new Address({street: '1234 Rainbow Road'}));
The second version is good enough as design choice for a single property.
It doesn't look like the case for full-fledged DI container, but if the number of properties is big enough, some kind of class map may help to eliminate boilerplate code.
class Person {
constructor(obj, classMap) {
for (let prop of Object.keys(classMap)) {
this[prop] = new classMap[prop](obj[prop]);
}
}
}
let p = new Person(obj, { address: Address });
Related
I have two objects inst1, inst2 which are both instances of the same class. If I use
inst2 = JSON.parse(JSON.stringify(inst1));
now if I change values of properties of inst2, values in inst1 do not change. That is great.
But sadly methods of inst2 have disappeared. So if I do
inst2.method1();
I get the error
"inst2.method1 is not a function"
Is there some way I can copy the values in an instance without destroying methods?
(obviously I could laboriously copy each value. I am trying to avoid that because I am lazy.)
I have tried to follow typescript - cloning object but I cannot make it work-
Ok, I have played a little since the provided answers are not 100% clear.
If you want to have a shallow copy and copy the methods too, you can use Object.create.
Again: If your object is simple enough, Object.create will be sufficient for you
const originalPerson = new Person("John");
originalPerson.address = new Address("Paris", "France");
const newPerson = Object.create(originalPerson);
/// this will be true
const isInstanceOf = newPerson instanceof Person;
//this will change the property of the new person ONLY
newPerson.name = "Peter";
//methods will work
newPerson.someMethod();
//methods will work even on nested objects instances
newPerson.address.anotherMethod();
// BUT if we change the city on any of the instances - will change the address.city of both persons since we have done a shallow copy
newPerson.address.city = "Berlin";
I have created typescript playground (just remove the types) to show it works and the drawback with its usage - link to the playground
Another approach is the class itself to have a clone method and to be responsible for its own cloning logic. An example follows, along with a link to another playground
class Address {
constructor(city, country) {
this.city = city;
this.country = country;
}
clone() {
// no special logic, BUT if the address eveolves this is the place to change the clone behvaiour
return Object.create(this);
}
getAddressDetails() {
return `City: ${this.city} country ${this.country}`;
}
}
class Person {
constructor(name, address) {
this.name = name;
this.address = address;
}
clone() {
const newInstance = Object.create(this);
//clone all other class instances
newInstance.address = this.address.clone();
return newInstance;
}
getPersonDetails() {
//calling internally address.getAddressDetails() ensures that the inner object methods are also cloned
return `This is ${this.name}, I live in ${this.address.getAddressDetails()}`
}
}
const originalAddress = new Address("Paris", "France");
const originalPerson = new Person("John", originalAddress);
const clonedPerson = originalPerson.clone();
clonedPerson.name = "Peter";
clonedPerson.address.city = "Berlin";
clonedPerson.address.country = "Germany";
// Log to console
console.log(`Original person: ${originalPerson.getPersonDetails()}`)
console.log(`Cloned person: ${clonedPerson.getPersonDetails()}`)
You should use structured cloning, see this answer:
How do I correctly clone a JavaScript object?
The reason that your current code isn't working is because you are parsing a stringified json object. Json stringify will remove all of the methods of an object and only stringify the objects values.
I came back to this at a convenient point and made quite a bit of progress by combining some of the above answers. The general purpose cloner was getting quite ugly (see below) and still not working (for arrays of class-objects) when I realised that it would be impossible to write a general purpose cloner.
I use the term class-object to mean an object defined by a class.
If a class-object contains a variable which itself is type class-object, call it subObj, then the general purpose cloner cannot know whether 1) it should copy subObj or 2) it should create a new instance of subObj and copy into the sub-properties. The answer depends on the meaning in the class.
In the first case above subObj. is just a pointer to another instance of subObj.
Therefore I strongly agree with the second part of Svetoslav Petkov's answer that the "class itself [should] have a clone method and be responsible for its own cloning logic.".
For what it's worth this is as far as I got with a general purpose cloner (in TypeScript). It is adapted from the other answers and creates new instances of class-objects liberally:
public clone(): any {
var cloneObj = new (this.constructor as any)() as any;
for (var attribut in this) {
// attribut is a string which will take the values of the names of the propertirs in 'this'
// And for example, if aNumber is a property of 'this' then
// this['aNumber'] is the same as this.aNumber
if (typeof this[attribut] === "object") {
let thisAttr = this[attribut] as any;
let cloneAttr = cloneObj[attribut] as any;
if (this[attribut] instanceof Array) {
for (let i in thisAttr) {
cloneAttr[i] = thisAttr[i]; // **** will not work on arrays of objects!!
}
continue; // to next attrib in this
}
if (this[attribut] instanceof Date) {
cloneAttr.setTime(thisAttr.getTime());
continue; // to next attrib in this
}
try {
cloneObj[attribut] = thisAttr.clone();
//cloneObj[attribut] = this.clone(); // with this, (from https://stackoverflow.com/questions/28150967/typescript-cloning-object) stack just gets bigger until overflow
}
catch (err) {
alert("Error: Object " + attribut + " does not have clone method." +
"\nOr " + err.message);
}
} else {
cloneObj[attribut] = this[attribut];
}
}
return cloneObj;
}
While trying to find a way to use nested classes in JS, I came up with this sort of thing:
class Character {
constructor() {
this.Info= class I {
constructor(name,value) {
this.name=name;
this.value=value;
}
};
}
bar () {
var trial = new this.Info("Goofy", 2);
alert(trial.name);
}
}
var test = new Character();
test.bar();
and it seems to work. However, I'm afraid this might be creating a new function object for each new call, as I define the class in the constructor (which is executed at each new call). Is there a more efficient way of doing this?
This question does not solve my issue as the author only wonders how to even have a nested class; I'm already able to do that but I wonder if there's a more efficient way.
Using a static property in react, angular or just using babel, because direct static class properties are not currently implemented on all browsers.
class Character {
static Info = class I {
constructor(name) { this.name=name; }
}
bar () {
return new Character.Info("Goofy");
}
}
const test = new Character();
console.log(test.bar());
Using a static property the old way -- currently working on all browsers.
class Character {
bar () { return new Character.Info("Goofy"); }
}
Character.Info = class I {
constructor(name) { this.name=name; }
}
const test = new Character();
console.log(test.bar());
Maybe the example you've given is too simple to demonstrate whatever problem you're trying to solve, but it seems to me you don't need to nest them at all.
class Info {
constructor(name, value) {
this.name = name;
this.value = value;
}
}
class Character {
bar() {
var trial = new Info("Goofy", 2);
console.log(trial.name);
}
}
const test = new Character();
test.bar();
I am using this code to make VN.CH.Mister1 using the VN.CH.Make() function inside the VN.CH object but feel like it is weird and inefficient and have been unable to find a more effective way of doing it, preferably all inside the object, while looking online.
let VN = {
CH: {
Make: function(name) {
this.Name = name;
this.Exists = 1;
}
}
};
VN.CH.Mister1 = new VN.CH.Make('Mister1');
If anyone could help explain a more effective way of doing this, or a way to do it all inside of the VN.CH object or point out any misunderstandings I might have about Javascript in general from what you see here I would greatly appreciate it.
If you need the Make constructor and it has to be on vn.ch, what you have is fine other than using non-standard naming. Obviously you can do what you like in your own code, and there is sometimes call to make an exception, but overwhelmingly the naming standards in JavaScript are that variables and properties start with a lower-case letter, so:
let vn = {
ch: {
Make: function(name) {
this.name = name;
this.exists = 1;
}
}
};
vn.ch.mister1 = new vn.ch.Make('Mister1');
If you don't need the Make constructor, you can do it more simply:
let vn = {
ch: {
mister1: {
name: "Mister1",
exists: 1
}
}
};
If the Make constructor doesn't have to be on vn.ch, then:
function Make(name) {
this.name = name;
this.exists = 1;
}
let vn = {
ch: {
mister1: new Make("Mister1")
}
};
I would separate the concerns of object creation and the place where the objects are stored. Here is an example of how you could achieve this (among many other ways):
let peopleObj = {} // Here is where you objects life
function make (name) { // Here is where you create them
this.name = name;
this.exists = 1;
}
peopleObj.Mister1 = new make('Mister1');
peopleObj.Mister2 = new make('Mister2');
console.log(peopleObj);
Seems like you want CH to manage some kind of collection of Person objects (i.e. the kind of object that VN.CH.Make creates). So, instead of writing
VN.CH.Mister1 = new VN.CH.Make('Mister1');
you could put a constructor function Make on VN.CH and write a function VN.CH.make which adds new instances.
CODE:
const VN = {
CH: {
Make: function Make(name) {
this.name = name;
this.exists = 1;
},
make(name) {
this[name] = new this.Make(name);
}
}
};
VN.CH.make('Mister1');
console.log(VN.CH.Mister1);
Ultimately however, I think it's clearer if you abstract your code by creating two classes with separate concerns:
PersonRegistry which creates Person instances and manages them; and,
Person which represents a person with a name and exists property
and then just make VN.CH point to an instance of PersonRegistry:
CODE:
class Person {
constructor(name) {
this.name = name;
this.exists = 1;
}
}
class PersonRegistry {
constructor() {
this.instances = {};
}
make(name) {
this.instances[name] = new Person(name);
}
get(name) {
return this.instances[name] || null;
}
}
const VN = {
CH: new PersonRegistry()
};
VN.CH.make('Lady1');
VN.CH.make('Mister1');
VN.CH.make('LadyA');
VN.CH.make('MisterA');
console.log(VN.CH.get('Lady1'));
console.log(VN.CH.get('Mister1'));
console.log(VN.CH.get('LadyA'));
console.log(VN.CH.get('MisterA'));
Note that you actually don't need the class declarations (you could just as well work with object literals); but, working with classes expresses your intentions more clearly imo and you can add subclasses later if needed.
So, I have a class:
class XYZ {
constructor(name) {
if (!new.target) new XYZ(name);
this.name = name;
}
run() {
}
walk() {
}
addShoe() {
// I want to add a shoe here IF it hasn't already been
// added, by any other instance
}
}
Now, I need all of my instances to share a variable, ie: SHOES.
The only way I am figuring to do this, well two ways is either add a variable outside the class ( top of module ) OR put it on the prototype.
ie
I've tried adding static variables, not working. Of course I could just use a set/get, but again - that is a method that would still have to "access/update" the "shared variable resource"..
let SHOES = new Set();
//or
XYZ.prototype.SHOES = new Set();
class XYZ {
constructor(name) {
if (!new.target) new XYZ(name);
this.name = name;
}
run() {
}
walk() {
}
}
is there a more intuitive way to do this? Of course, this is a very simplified version - but trying to go with es6 'way of doing things'.
I ended going with the following. but it still seems wrong. Your thoughts?
XYZ.SHOES = new Set();
I believe I also tried the following and not working:
class XYZ {
constructor() {
static SHOES = new Set(); // <-- didn't work
this.constructor.SHOES = new Set(); // <-- didn't work
}
}
XYZ.SHOES = new Set(); // <- worked
let SHOES = new Set(); // <-- worked
Of course, declaring a global in the module worked AND just assigning to the class worked, but these seem dirty. Any thoughts on a more succinct approach?
I'm writing some JS that extends a parent class and I wanted to know if there's a way to tell if a child class is using a parent method without having called it yet. Ideally I'd like to run a check in the constructor of the parent to see if any of the child methods are using the parent's methods in the method definition.
I've done a bit of research and have come across things like Object.getOwnPropertyNames() but I'm not sure if I'm headed in the right direction.
For instance:
class Path {
constructor (name) {
// how can I check if addRelationship have been used? If possible.
this.relationships = {};
this.currentRelationship = '';
this.path = path;
}
addRelationship (relationship) {
// do something
this.currentRelationship = relationship.path;
return this;
}
makePath () {
let path = [this.path];
if(this.currentRelationship) {
path.push(this.currentRelationship)
}
return path.join("/");
}
}
class OnePath extends Path {
// ...
someMethodFromThatRelationship () { }
}
class TwoPath extends Path {
// ...
}
var onePath = new OnePath('one');
var twoPath = new TwoPath('two-path');
class SomeOtherPath extends Path {
one () {
return this.addRelationship(onePath);
}
two () {
return this.addRelationship(twoPath);
}
}
The idea of the above example is I could check if addRelationship is referenced in any methods and if so, register a this.relationships.one and this.relationships.two before one() and two() are actually called. I hope I'm making sense. I'd love to know if this is even possible.
Updated
The end result of the above code would be the ability to do the following:
let someOtherPath = new SomeOtherPath('some-other-path');
// now I can call
someOtherPath.relationships.one.someMethodFromThatRelationship();
// and can also call the save method from the extended class
someOtherPath.one().makePath();
// some-other-path/one
// I can also just call
someOtherPath.makePath();
// some-other-path
Is there a way to tell if a child class is using a parent method without having called it yet?
No. Figuring out what programs do without calling them is equivalent to the unsolvable halting problem.
I think what you are actually looking for is a more declarative approach for creating the relationship and its accompanying method in one go. Don't use too much magic (which a parent constructor inspecting its child class code would certainly be) but be explicit.
class Path {
constructor (path) {
this.relationships = {};
this.currentRelationship = '';
this.path = path;
}
addRelationship (name, relationship) {
this.relationships[name] = relationship;
this[name] = function() {
// do something
this.currentRelationship = name;
return this.relationships[name];
}
return this;
}
makePath () {
let path = this.path;
if (this.currentRelationship) {
path += "/" + this.relationships[this.currentRelationship].makePath();
}
return path;
}
}
class SomeOtherPath extends Path {
constructor(name) {
super(name);
this.addRelationship("one", new OnePath('one'));
this.addRelationship("two", new TwoPath('two-path'));
}
}
or even
class Path {
constructor (path, relationships = {}) {
this.relationships = relationships;
this.currentRelationship = '';
this.path = path;
for (let const r in relationships)
this.addRelationship(r, relationships[r]);
}
…
}
class SomeOtherPath extends Path {
constructor(name) {
super(name, {
one: new OnePath('one'),
two: new TwoPath('two-path')
});
}
}
Maybe you don't even need these child classes any more if they don't have other methods or are only instantiated once (as singletons).
Notice that the above approach will create new methods and new subpaths on every instantiation of the constructor, if you don't want that you can of course also put the declaration on the class statically. Just make addRelationShip a static method that initialises the default relationships objects and puts the methods on the class' .prototype. The variations of the pattern are endless.
You even might want to experiment with the proposed decorators feature for classes.