Calculated Class property from another Class Property (ES6) - javascript

I have a class with a constructor and as below:
class Bookshelf {
constructor(author, publisher) {
this.books = [],
this.numBooks = this.books.length
this.author = author,
this.publsiher = publisher
}
}
I am not getting back the value of the book array which is what I truly want to do. I know this is causing the value not to be grabbed but I really want to understand why?

I interpret this question, based on the title, to be asking why numBooks doesn't update after you add an entry to books within a Bookshelf you create.
When you run the statement this.numBooks = this.books.length, it does set a property called numBooks to the calculated value this.books.length, which is 0. Later, you might add a value to books, but that doesn't affect numBooks.
The problem isn't about calculating one "class property" from another so much as understanding that numBooks stores a number, and that number doesn't update automatically. Your constructor runs once, and that's it.
Compare with this:
class Bookshelf {
constructor(author, publisher) {
this.books = [];
this.author = author;
this.publisher = publisher;
}
getNumBooks() {
return this.books.length;
}
}
let myBookshelf = new Bookshelf("Isaac Asimov", "Bantam Press");
myBookshelf.books.push("I, Robot");
console.log(myBookshelf.getNumBooks()); // prints 1
Because your call to getNumBooks() runs code, it can calculate the dynamic value this.books.length, which is why it gives you an up-to-date value where the property numBooks doesn't update automatically. If you'd rather keep numBooks as a property, you also have the option of making the books property private, and instead exposing an addBook function that adds a book and resets numBooks accordingly.
As Patrick reminds me in the comments, since you specified ES6, you can use getter syntax to write a function that acts like a property. This works because it runs code when you refer to numBooks, even though the syntax hides that detail away.
class Bookshelf {
constructor(author, publisher) {
this.books = [];
this.author = author;
this.publisher = publisher;
}
get numBooks() {
return this.books.length;
}
}
let myBookshelf = new Bookshelf("Isaac Asimov", "Bantam Press");
myBookshelf.books.push("I, Robot");
console.log(myBookshelf.numBooks()); // prints 1

Related

JavaScript / TypeScript: Difference between `Object.assign({}, myClass)`/`Object.create(myClass)` and constructor `new MyClass()`

I wish I could post my project verbatim for this question, but I cannot.
Essentially, I have the following classes:
class Lowest {
someValue: string
constructor(someValue: string) {
this.someValue = someValue
}
}
class Middle {
lowest: Lowest
constructor(someValue: string) {
this.lowest = new Lowest(someValue)
}
}
class Highest {
private _middle: Middle
otherValue: SomeOtherClass
// normal getter
public get middle(): Middle {
return this._middle
}
public set middle(next: Middle) {
this._middle = next
// notice: when `_middle` is set, I update another property!
otherValue.doSomething(this._middle)
}
constructor(config: { someValue: string }) {
this.middle = new Middle(config.somevalue)
}
}
In some places in my program, I have a reference to a Highest instance and need to alter its middle.lowest.someValue sub-property. Now, for architectural reasons that I cannot really describe here, I need to update the Highest.otherValue property whenever Highest.middle. As I am using TypeScript, I simply perform this operation in the setter for Highest.middle. As such, I cannot just straight up set Highest.middle.lowest to some value.
My first approach was:
const nextMiddle = Object.assign({}, highestInstance.middle)
nextMiddle.lowest.someValue = "some other thing"
highestInstance.middle = nextMiddle
However, this ended up causing some very strange issues. Now, I had no real technical need for performing a deep clone of nextMiddle, so I overcame it with the following code:
const nextMiddle = highestInstance.middle
nextMiddle.lowest.someValue = "some other thing"
highestInstance.middle = nextMiddle
While I was experimenting with the best fix, I implemented a Middle.copy() method that basically just calls new Middle() and new Lowest() with the values from the previous instance. This also solved my technical issues, but left me even more confused.
I understand that there's a big difference between simply re-assigning highestInstance.middle and using Object.assign() to clone it, but I don't understand why there does not seem to be a difference between Object.assign() and new Middle()
What are the real differences with these three methods of cloning/re-assignment?
had no real technical need for performing a deep clone of nextMiddle,
so I overcame it with the following code:
Object.assign({}, highestInstance.middle) is creating a shallow copy and not a deep copy.
The issue here is using Middle Setter is forcing yourself to perform otherValue: SomeOtherClass only when middle is updated.
it will update it even if you just do just :
high.middle.lowest.someValue = 'new value'
high.middle = high.middle; // setter triggered
A possible solution is to create a call-back chain rather than using a setter:
type voidFun = () => void;
class Lowest {
private _someValue: string
public get someValue(): string {
return this._someValue
}
private update?: voidFun; // create an optional callback
public set someValue(next: string) {
this._someValue = next
this.update?.call(undefined);
}
constructor(someValue: string, fun?: voidFun) {
this._someValue = someValue
this.update = fun;// pass down
}
}
class Middle {
lowest: Lowest;
constructor(someValue: string, fun?: voidFun) {
this.lowest = new Lowest(someValue, fun)
}
}
class Highest {
private _middle: Middle
otherValue: any = {};
// normal getter
public get middle(): Middle {
return this._middle
}
// now you dont need middle setting so it cant be updated by anyone directly
// if you still want to have you can have it too
private callBack = () => {
console.log('lower was update');
this.otherValue.myMiddle = this._middle.lowest.someValue;
}
constructor(config: { someValue: string } = { someValue : 'constr' }) {
this._middle = new Middle(config.someValue, this.callBack)
}
}
let high = new Highest()
high.middle.lowest.someValue = 'new value'
// high.middle = high.middle; // now not possible unless you create setter
console.log('updated middle high',high)

Why does this getter method has been called several times in React?

I have a Store which will be provided to the component. In this Store file, there are several getter function. But I find only this getter function will be executed three times since this.rawMonthlyImpacts will be only changed once when the api get response from backend. I am so confused because other getter function in this file will be only executed once. During every execution, this.rawMonthlyImpacts is always same. Because this function is time-consuming, so I want to figure out why this happens. Hope you can give me some advice. Thanks!
get Impacts(){
const monthlyImpacts = new Map<string, Map<string, number>>();
if (this.rawMonthlyImpacts) {
this.rawMonthlyImpacts.forEach((impact) => {
if (impact.Impact > 0) {
const month = TimeConversion.fromTimestampToMonthString(impact.Month);
const tenantId = impact.TenantId;
const tenantImpact = impact.Impact;
if (!monthlyImpacts.has(month)) {
const tenantList = new Map<string, number>();
monthlyImpacts.set(month, tenantList.set(tenantId, tenantImpact));
} else {
const tenantWithImpactMap = monthlyImpacts.get(month);
if (!tenantWithImpactMap.has(tenantId)) {
tenantWithImpactMap.set(tenantId, tenantImpact);
} else {
tenantWithImpactMap.set(tenantId, tenantWithImpactMap.get(tenantId) + tenantImpact);
}
monthlyImpacts.set(month, tenantWithImpactMap);
}
}
});
}
return monthlyImpacts;
},
Update: I have find that there are other two functions use this.Impacts. If I remove these two functions, the getter function will only be executed only once. I think the getter function uses the cache to store data, so once the data is calculated for the first time, subsequent calls to the getter function should not be re-executed, only the value in the cache needs to be retrieved. So I am very confused about why this getter function will be executed 3 times.
getImpactedTenants(month: string): string[] {
return Array.from(this.Impacts.get(month).keys());
},
get overallMonthlyImpactedTenants(): Map<string, number> {
return new Map<string, number>(
Array.from(this.Impacts)?.map((monthEntries) => {
const month = monthEntries[0];
const impactedTenants = monthEntries[1].size;
return [month, impactedTenants];
})
);
}
Hard to tell what exactly is happening without more context, but remember that with a get function, every single time you reference that property (.Impacts in this case) the get function will be called.
Assuming that each impact stored in this.rawMonthlyImpacts which you loop through is an instance of the class with this getter, then as far as I'm aware, you are calling the get function each time you reference impact.Impacts, such as in the conditional:
if (impact.Impact > 0) {
I might be way off though; I'm unfamiliar with React and so my answer is based only on my experience with vanilla JS.

How to include or detect the name of a new Object when it's created from a Constructor

I have a constructor that include a debug/log code and also a self destruct method
I tried to find info on internet about how to detect the new objects names in the process of creation, but the only recommendation that I found was pass the name as a property.
for example
var counter = {}
counter.a =new TimerFlex({debug: true, timerId:'counter.a'});
I found unnecessary to pass counter.a as a timerId:'counter.a' there should be a native way to detect the name from the Constructor or from the new object instance.
I am looking for something like ObjectProperties('name') that returns counter.a so I don't need to include it manually as a property.
Adding more info
#CertainPerformance What I need is to differentiate different objects running in parallel or nested, so I can see in the console.
counter.a data...
counter.b data...
counter.a data...
counter.c data... etc
also these objects have only a unique name, no reference as counter.a = counter.c
Another feature or TimerFlex is a method to self desruct
this.purgeCount = function(manualId) {
if (!this.timerId && manualId) {
this.timerId = manualId;
this.txtId = manualId;
}
if (this.timerId) {
clearTimeout(this.t);
this.timer_is_on = 0;
setTimeout ( ()=> { console.log(this.txtId + " Destructed" ) },500);
setTimeout ( this.timerId +".__proto__ = null", 1000);
setTimeout ( this.timerId +" = null",1100);
setTimeout ( "delete " + this.timerId, 1200);
} else {
if (this.debug) console.log("timerId is undefined, unable to purge automatically");
}
}
While I don't have a demo yet of this Constructor this is related to my previous question How to have the same Javascript Self Invoking Function Pattern running more that one time in paralel without overwriting values?
Objects don't have names - but constructors!
Javascript objects are memory references when accessed via a variables. The object is created in the memory and any number of variables can point to that address.
Look at the following example
var anObjectReference = new Object();
anObjectReference.name = 'My Object'
var anotherReference = anObjectReference;
console.log(anotherReference.name); //Expected output "My Object"
In this above scenario, it is illogical for the object to return anObjectReference or anotherReference when called the hypothetical method which would return the variable name.
Which one.... really?
In this context, if you want to condition the method execution based on the variable which accesses the object, have an argument passed to indicate the variable (or the scenario) to a method you call.
In JavaScript, you can access an object instance's properties through the same notation as a dictionary. For example: counter['a'].
If your intent is to use counter.a within your new TimerFlex instance, why not just pass counter?
counter.a = new TimerFlex({debug: true, timerId: counter});
// Somewhere within the logic of TimerFlex...
// var a = counter.a;
This is definitely possible but is a bit ugly for obvious reasons. Needless to say, you must try to avoid such code.
However, I think this can have some application in debugging. My solution makes use of the ability to get the line number for a code using Error object and then reading the source file to get the identifier.
let fs = require('fs');
class Foo {
constructor(bar, lineAndFile) {
this.bar = bar;
this.lineAndFile = lineAndFile;
}
toString() {
return `${this.bar} ${this.lineAndFile}`
}
}
let foo = new Foo(5, getLineAndFile());
console.log(foo.toString()); // 5 /Users/XXX/XXX/temp.js:11:22
readIdentifierFromFile(foo.lineAndFile); // let foo
function getErrorObject(){
try { throw Error('') } catch(err) { return err; }
}
function getLineAndFile() {
let err = getErrorObject();
let callerLine = err.stack.split("\n")[4];
let index = callerLine.indexOf("(");
return callerLine.slice(index+1, callerLine.length-1);
}
function readIdentifierFromFile(lineAndFile) {
let file = lineAndFile.split(':')[0];
let line = lineAndFile.split(':')[1];
fs.readFile(file, 'utf-8', (err, data) => {
if (err) throw err;
console.log(data.split('\n')[parseInt(line)-1].split('=')[0].trim());
})
}
If you want to store the variable name with the Object reference, you can read the file synchronously once and then parse it to get the identifier from the required line number whenever required.

Getting an object within an object within an object

In Javascript I have an array of clients. Each client is an object that has inside an array of vehicles which are also objects. Now I need to get another array of objects named trabajos that should be nested inside the vehicles. All should be stored in the localStorage, but I am not sure how I should start getting the trabajos inside the vehicles. Here is how I gotten the first two parts.
function getClientes(){
let listaClientes = JSON.parse(localStorage.getItem('listaClientesLS'));
let clientes = [];
if (listaClientes == null) {
clientes = [];
} else{
listaClientes.forEach(obj =>{
let objCliente = new Cliente(obj.nombre, obj.apellido1, obj.apellido2, obj.cedula, obj.telefono, obj.email);
obj.listaVehiculos.forEach(objVehiculoTemp => {
let objVehiculo = new Vehiculo(objVehiculoTemp.matricula,objVehiculoTemp.marca,objVehiculoTemp.modelo,objVehiculoTemp.anno,objVehiculoTemp.capacidad,objVehiculoTemp.kilometraje);
objCliente.agregarVehiculo(objVehiculo);
});
clientes.push(objCliente);
})
}
return clientes;
}
The github repository (the question is not minimal) suggests jobs ("trabajos") for a vehicle should be stored in the listaTrabajos array property of a vehicle object, placed there by the add job method (agregarTrabajo) of a the vehicle class, called from the job registration function regTrabajo.
But the vehicle class does not define the agregarTrabajo method. I expect regTrabajo throws an error when called. (Note errors on the console should be solved before anything else! You can ask a question about an error if you can't solve it.)
Add the agregarTrabajo method to the Vehiculo class, e.g.
class Vehiculo {
constructor(....
//... constructor function
}
agregarTrabajo (pObjTrabajo) {
this.listaTrabajos.push(pObjTrabajo);
}
}
Now the list of jobs can be accessed using the listaTrabajos property of the objVehiculoTemp argument of the function processing each vehicle in the post. I don't know what processing is required:
obj.listaVehiculos.forEach(objVehiculoTemp => {
let objVehiculo = new Vehiculo(objVehiculoTemp.matricula,objVehiculoTemp.marca,objVehiculoTemp.modelo,objVehiculoTemp.anno,objVehiculoTemp.capacidad,objVehiculoTemp.kilometraje);
// process objVehiculoTemp.listaTrabajos
objVehiculo.listaTrabajos = objVehiculoTemp.listaTrabajos; // FOR EXAMPLE
objCliente.agregarVehiculo(objVehiculo);
});

Understanding prototypes and "classes"

Coming from a low level C background, I'm having difficulty understanding this prototype, function constructor, and "classical classing" constructs.
For the purpose of learning, I've tried to design a menu system.
'menuItem' should have a 'name' property, and a 'remove' function.
'food' should be based on menuItem, and should have no custom properties or functions for the sake of simplicity.
'drink' should be based on menuItem, and and should have no custom properties or functions for the sake of simplicity.
'menu' should contain an array of 'food's, 'drink's and corresponding functions to add new 'food's and 'drink's.
The result should be usable like so:
var testmenu = new menu();
testmenu.addfood("burger");
testmenu.addfood("chips");
testmenu.adddrink("fanta");
testmenu.adddrink("coke");
alert(JSON.stringify(testmenu));
testmenu.foods[0].remove();
alert(JSON.stringify(testmenu));
This is what I came up with:
function menu() {
var that = this;
this.foods = new Array();
this.drinks = new Array();
this.addfood = function(name) {
that.foods[that.foods.length] = new food(name);
// this seems silly, is there a way of getting the index in the array without giving it an index property?
that.foods[that.foods.length - 1].index = that.foods.length - 1;
// can't store the menu
//that.foods[that.foods.length - 1].menu = that;
return that.foods.length - 1;
}
this.adddrink = function(name) {
that.drinks[that.drinks.length] = new drink(name);
// same problem as with food
that.drinks[that.drinks.length - 1].index = that.drinks.length - 1;
//that.drinks[that.drinks.length - 1].menu = that;
return that.drinks.length - 1;
}
}
var menuItem = {
name: "New menuItem",
remove: function() {
alert("Remove: " + this.name + " at index " + this.index);
// No way of telling what menu the menuItem is in, I have to assume testmenu
testmenu[this.menuItemType].splice(this.index, 1);
//this.menu[this.menuItemType].splice(this.index, 1);
}
};
function food(name) {
if(name) this.name = name;
this.menuItemType = "foods";
}
food.prototype = menuItem;
function drink(name) {
if(name) this.name = name;
this.menuItemType = "drinks";
}
drink.prototype = menuItem;
var testmenu = new menu();
testmenu.addfood("burger");
testmenu.addfood("chips");
testmenu.adddrink("fanta");
testmenu.adddrink("coke");
alert(JSON.stringify(testmenu));
testmenu.foods[0].remove();
alert(JSON.stringify(testmenu));
There are two problems I am having:
there is no way for the remove function to know which menu to remove the menuItem from.
there is no way to get the index of an array item, you have to store it as a property.
Your prototypes and "classes" look quite fine.
there is no way for the remove function to know which menu to remove the menuItem from.
Sure, how would you know? You might want to pass it as a parameter. However, the more OOP way would be to put the remove method on the menu class (the collection, on which also the add methods are defined) and to pass the item as an argument, instead of having it on the menuitem.
// can't store the menu
//that.foods[that.foods.length - 1].menu = that;
What stops you? Sure, JSON.stringify throws an error if you pass in an object with cyclic references. You might want to use a debugger, or at least check the error console. Or try to do console.log(testmenu) instead of the alert, so that you don't need to serialize it but can dynamically inspect it.
there is no way to get the index of an array item, you have to store it as a property.
Well, you could search for it using the indexOf array method if you don't want to store the index. It would become inaccurate anyway as soon as you remove other items.

Categories

Resources