Metaprogramming in JavaScript/TypeScript Part #1
Daniel
Posted on August 15, 2022
Descriptor.
I would like to present to you a series of mini-articles, which describe the metaprogramming basics and techniques.
Generally, I will write about the use of certain techniques in JavaScript or TypeScript
This is the first article in the series
So, what is metaprogramming?
Metaprogramming is a programming technique in which computer programs have the ability to treat other programs as
their data. It means that a program can be designed to read, generate, analyze or transform other programs, and even
modify itself while running. In some cases, this allows programmers to minimize the number of lines of code to express a
solution, in turn reducing development time.
The definition is pretty complex, but the main benefit of metaprogramming is quite clear:
“… this allows programmers to minimize the number of lines of code to express a solution, in turn reducing development time”.
Actually, metaprogramming has so many forms of practical application, that the discussion on “where metaprogramming ends
and the programming begins” might never get an accurate answer.
I have formulated the following rules for myself:
1) Metaprogramming should not define, change or affect the business logic by any means.
2) If the metaprogramming-related code is removed, the program should not be seriously affected.
Descriptor
In JavaScript, metaprogramming is a fairly new trend, having the descriptor as one of its fundamentals.
Descriptor is a kind of description (meta information) of a given property/method in an object.
Understanding and correctly application of descriptors will allow you a lot more than just create and modify methods or
properties in your objects.
Descriptors are also helpful in understanding how to operate with decorators, which is the topic of my next article.
Let’s take an apartment and describe it as an object, with it’s own properties:
let apt = {
floor: 12,
number: '12B',
size: 3400,
bedRooms: 3.4,
bathRooms: 2,
Price: 400000,
amenities: {...}
};
Let’s determine which properties can and cannot be changed.
For example, it is impossible to change the floor or the total size of the apartment, while the number of rooms or
bathrooms can be changed.
As a result, we have the following requirements: to make it impossible to change the floor and size properties in apt
objects.
To solve this task, we will need descriptors for each of these properties. To get the descriptor, use the static method
getOwnPropertyDescriptor, which belongs to the Object class*.*
let descriptor = Object.getOwnPropertyDescriptor(apt, 'floor');
console.log(descriptor);
// Output
//{
// value: 12,
// writable:true,
// enumerable:true,
// configurable:true
//}
Let’s take a closer look at the next:
value — this is the value assigned to the floor property at a certain moment
writable — defines whether it is possible to change the value
enumerable — defines if the floor property can or can’t be enumerated — (more on this later).
configurable — defines the ability to make changes on the descriptor object.
To prevent the floor property from changing after initialisation, it is necessary to set the writable value to false.
The static defineProperty method is used to change descriptor properties. Its input parameters are the object itself (
apt), property name (‘floor’) and the descriptor.
Object.defineProperty(apt, ‘floor’, {writable: false});
In this case, we pass not the entire descriptor object, but only the writable property having the false value.
Now let’s try to change the floor property value:
apt.floor = 44;
console.log(apt.floor);
// output 12
The value has not changed, and when using the ‘use strict’, an error message will be shown:
” Cannot assign to read only property ‘*floor’ of object ‘…*
Now it is impossible to change the value. However, we can still revert the writable to true and modify the floor
property value. To avoid this, we have to set the configurable property value to false
Object.defineProperty(apt, 'floor', {
writable: false, configurable: false
})
Let’s make one more attempt to change the value of any of the property of our descriptor…
Object.defineProperty(apt, 'floor', {
writable: true, configurable: true
});
We get the next:
“TypeError: Cannot redefine property: floor…”
Let’s sum it up:
To make the value of the property unchangeable, it is necessary to specify the configuration of this property:
{writable: false, configurable: false}
.
//This can be done when initialising the property:
Object.defineProperty(apt, 'floor',
{value: 12, writable: false, configurable: false});
//Or after the initialisation:
Object.defineProperty(apt, 'floor',
{writable: false, configurable: false});
In the end, let’s consider an example with a class:
class Apartment {
constructor(apt){
this.apt = apt;
}
getFloor(){
return this.apt.floor
}
}
let apt = {
floor: 12,
number: '12B',
size: 3400,
beds: 3.4,
baths: 2.
};
Let’s change the getFloor method:
Apartment.prototype.getFloor = () => {
return 44.
};
let myApt = new Apartment(apt);
console.log(myApt);
// output will be changed.
// 44
Now let’s change descriptor of the getFloor
method
Object.defineProperty(Apartment.prototype, 'getFloor',
{writable: false, configurable: false});
Apartment.prototype.getFloor = () => {
return 44.
};
let myApt = new Apartment(apt);
console.log(myApt);
// output will be original.
12
Back to enumerable.
let otherApt = {
floor: 12,
number: '12B',
size: 3400,
bedRooms: 3.4,
bathRooms: 2,
price: 400000,
amenities: {}
};
let keys = Object.keys(otherApt);
console.log(keys);
// Output
[ 'floor', 'number', 'size', 'bedRooms',
'bathRooms', 'price', 'amenities'
]
In other words, we can iterate the properties of the object using for … in.
What if one of the properties, e.g. amenities, should not be available for iteration?
This can be done by setting {enumerable: false} in our descriptor
Object.defineProperty(otherApt, 'amenities', {enumerable: false});
const keys = Object.keys(otherApt);
console.log(keys);
// Output
// [ 'floor', 'number', 'size', 'bedRooms', 'bathRooms', 'price' ]
The amenities property remains a part of the apt.prototype.
console.log(otherApt.hasOwnProperty('amenities'));
// Output
// true
Takeaway
I hope this article will shed a little more light on what descriptor is and how you can use it.
If you like this article press like button ♾️ times.
Feel free to ask a questions.
Follow me on Twitter and Medium and my website for blog updates.
Thanks a lot for reading!
Posted on August 15, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024
November 30, 2024