In one of the previous lessons, we implemented the Money constructor. Remember its interface:
const money1 = new Money(100);
money1.getValue(); // 100
money1.format(); // "$100"
// Doesn't change money1 itself
money1.exchangeTo('eur').getValue(); // 70
const money2 = new Money(200, 'eur');
const money3 = money2.add(money1); // 270
The amount of money and the currency are part of the specific object, but what about the rates? Below is an example of a possible constructor implementation:
class Money {
constructor() {
this.rates = {
usd: {
eur: 0.7,
},
eur: {
usd: 1.2,
},
};
}
}
With this class definition, each newly created object will get its own copy of the conversion rate information. Technically, this code works, but logically, it's wrong. Rates have nothing to do with a specific Money object, they determine the behavior of all Money. Imagine if we needed to expand the number of currencies or change rates while the program was running. This is possible if the rates are calculated dynamically, as in real life. This means that everything should change without having to stop the program. Since all these parameters are bound to each object individually, you would have to recreate all the objects or restart the program.
To solve this problem, we use constructor functions. Any function in JavaScript is an object, and any property added to a constructor function is available in all of its objects:
// No matter how Money itself is defined. This can be a normal constructor function or a class
// One way or another, any class inside JS is a function constructor + prototype filled with functions
Money.rates = {
usd: {
eur: 0.7,
},
eur: {
usd: 1.2,
},
};
Accessing the constructor property differs from calling the normal properties of the object itself. There are two main ways. The first is direct, via the Money.rates
constructor function. This is the easiest way, but then you have to duplicate the name of the constructor
function. The second is via the constructor property. It's a special property that gives direct access to the constructor from objects. This is the preferred method when we're inside an object:
class Money {
constructor(value, currency = 'usd') {
this.value = value;
this.currency = currency;
}
exchangeTo(newCurrency) {
if (this.currency === newCurrency) {
return new Money(this.value, this.currency);
}
// this.constructor.rates is in the constructor function
const newValue = this.value * this.constructor.rates[this.currency][newCurrency];
return new Money(newValue, newCurrency);
};
// Other methods
}
With this approach, we have separated the concerns. The Money object itself is responsible only for its own data. The constructor's concern is general things. This allows you to change Money parameters for all objects at once:
Money.rates.usd.eur = 0.71;
You can go even further and update the data not directly, but through methods:
Money.setRate = function setRate(from, to, value) {
// Here we address it directly, because we are in the context of the Money object (aka constructor function)
this.rates[from][to] = value;
}
Money.setRate('usd', 'gbp', 0.6);
But be careful. Everything in the constructor function has actually become a global state. Any change is reflected on all objects at once. Sometimes this can be a good thing, as in our case, but in other cases, it can lead to the data desynchronization. Especially when processes are separated in time (asynchronous code).
Static
Such functionality is implemented using static properties and methods. Here's what it looks like:
class Money {
// Defining the static property
static rates = {
usd: {
eur: 0.7,
},
eur: {
usd: 1.2,
},
};
// Defining the static method
static setRate(from, to, value) {
this.rates[from][to] = value;
}
}
// The usage is exactly the same as in the examples above
Money.rates.usd.eur; // 0.7
Money.setRate('usd', 'eur', 0.8);
Money.rates.usd.eur; // 0.8
// From within objects, you can refer to this.constructor
Static, like classes, are just sugar over functions. But they are becoming popular because they make code cleaner.
Recommended materials
Are there any more questions? Ask them in the Discussion section.
The Hexlet support team or other students will answer you.
For full access to the course you need a professional subscription.
A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.