Javascript is a weird and funny language, yet so beautiful and powerful. Javascript developers are always striving to learn something new and even if we have used it for years, we keep discovering new features & tricks every other time. This article is an attempt to unfold and revisit such features and tricks that are sparingly used but are really handy and useful.
Let’s dive in.
1. Getters and Setters
Getters and Setters allow us to have more control over object properties by controlling how to set & access them. Sometimes, we may want to perform an operation before/after the value has been updated or accessed. Getters and Setters provide a perfect way to do this. A Setter is automatically invoked on assignment and Getter is invoked when a property is accessed. get and set are the keywords for defining getters and setters respectively.
Let’s see how they work:
const user = {
_age: 20,
set age(value) {
// Adding checks before assigning the value
if (typeof value !== 'number' || value <= 0 || value > 100) {
console.log('Invalid Age');
return;
}
this._age = value;
},
get age() {
// We can do some processing & return modified value as we want
return `${this._age} years`;
}
}
user.age = '26'; // Invalid age -> Setter is invoked
user.age = 26; // Setter is invoked & value gets assigned as it is valid
console.log(user.age); // "26 years" -> Getter is invoked
What’s going on here?
We defined getters and setters for a property age in user object. As you can see, the property age does not directly exist in the object, rather we use another property _age to store & retrieve the actual value. Thus, age becomes a pseudo-property. Now, when we assign a value to user.age, the setter function is automatically invoked. It checks the conditions and assigns the value to _age property. Whenever we try to access user.age, the getter function invokes and returns a processed value using _age.
Okay, but how to define getters and setters on an already defined object?
With Object.defineProperty. Let’s see how that works:
const user = {
_age: 20
};
Object.defineProperty(user, 'age', {
set: function(value) {
if (typeof value !== 'number' || value <= 0 || value > 100) {
console.log('Invalid Age');
return;
}
this._age = value;
},
get: function() {
return `${this._age} years`;
}
});
user.age = '123'; // "Invalid Age"
user.age = 12;
console.log(user.age); // "12 years"
We can define Getters and Setters in classes as well, in the same way as defined above.
2. Unary + & – Operators
A unary operation is an operation with only one operand. Unary + & – operators attempt to convert the operand to number type, the only difference being the unary – makes the number negative.
Many a times, we encounter a scenario where we need to convert a string to a number. These operators come to the rescue by just prefixing them to the given string. Let’s see how they work:
+'78' // 78
+'text' // NaN
+'-7' // -7
+true // 1
+false // 0
+null // 0
+{} // NaN
+new Date(); // 1599153225988 -> Current Timestamp
// Similarly,
-'78' // -78
-'text' // NaN
-true // -1
-false // -0
-null // -0
-{} // NaN
3. Exponentiation ( ** ) Operator
This operator is useful to quickly find the power of a number raised to a certain number. If you want to find x to the power y, simply use x ** y.
console.log(2 ** 3); // 8 console.log(2 ** 3 ** 2); // 512 console.log((2 ** 3) ** 2); // 64
As an added advantage, it also accepts BigInts as operands.
4. in Operator
in operator can be used to quickly check if a specified property is present in an object.
Syntax: prop in object
const user = {
age: 23,
fullName: 'Test'
};
console.log('age' in user); // true
let colours = ['red', 'green', 'blue'];
console.log(2 in colours); // true
// As index 2 is present in array
console.log('green' in colours); // false
// You must specify the index number, not the value at that index
console.log('length' in colours); // true
// As length is an Array property
Keep in mind that the in operator returns true even if the property is present in the prototype chain.
console.log('toString' in {}); // true
5. new.target property
This property is useful to detect whether a function or constructor was called using the new operator. It gives us a reference to that class or constructor function. The new.target pseudo-property is available in all the functions. However, in normal function calls, new.target is undefined.
function User() {
if (!new.target) {
console.log('Normal function call');
} else {
console.log('Instance created using new operator');
}
console.log(new.target);
}
const testUser = new User();
// Instance created using new operator
// Logs the reference to function User
User();
// Normal function call
// undefined -> No reference
You may think, why do we need to check if an instance was created with new operator? Consider the following example:
let colour = 'red';
// Constructor function
const Car = function(colour) {
this.colour = colour;
};
const myCar = new Car('blue');
console.log(myCar.colour); // "blue"
console.log(window.colour); // "red"
const hisCar = Car('blue'); // Direct function call
console.log(hisCar.colour); // Error
console.log(window.colour); // "blue"
// overwritten the global variable: colour
As we saw above, the global variable colour got updated unknowingly as ‘this’ is pointing to a window inside the function, which is called directly. To avoid such mishaps, we can rely on new.target property.
6. String Replace can accept a callback function
Many a times, we use the replace method on strings with regex/string as the first parameter and string to be replaced as the second.
const newStr = str.replace(regexp|substr, replacement);
It replaces all the matches with the provided replacement string. However, we can have more control over the replacement by providing a callback function as a second parameter. Consider an example:
const output = '4 12 27 32 15'.replace(/d+/g, '#'); console.log(output); // # # # # #
This is a simple replacement, where we are replacing each number with ‘ # ’. What if we wanted to replace only the numbers > 20? We cannot simply do that with regex alone, right? We can achieve that with a callback function.
const output = '4 12 27 32 15'.replace(/d+/g, function(match) {
return +match > 20 ? '#' : match;
});
console.log(output); // 4 12 # # 15
Notice how we used the unary + operator to convert a string to a number 🙂.
7. Formatting JSON
While dealing with APIs, we need to stringify objects – a very common scenario for a web developer. We simply use JSON.stringify() to achieve that. You know how ugly it is to visualize such JSON strings 😅.
Look at the one below:
{"name":{"firstName":"John","lastName":"Doe"},"age":26,"experience":6,"category":{"user":{"permission":"all"}}}
And it keeps getting uglier when we have larger & deeply nested objects. However, JSON.stringify() method also allows us to indent such strings, for better readability.
It accepts two optional parameters.
- Replacer function – Allows us to filter the properties of an object to be stringified.
- Space – A number or a string used to insert white space into the output JSON string. With a number, you can specify the number of spaces you want and with string, space characters like ‘t’ (tab).
const output = JSON.stringify({
name: {
firstName: 'John',
lastName: 'Doe',
},
age: 26,
experience: 6,
category: {
user: {
permission: 'all'
}
}
}, null, 't');
console.log(output);
// Shows following formatted output:
{
"name": {
"firstName": "John",
"lastName": "Doe"
},
"age": 26,
"experience": 6,
"category": {
"user": {
"permission": "all"
}
}
}