Since the release of ES6 in 2015, JavaScript has been evolving fast with tons of new features coming out in each iteration. The new versions of the JavaScript language specification have been updated yearly, with new language feature proposal being finalized faster than ever. This means that new features are getting incorporated into modern browsers and other JavaScript run-time engines, like Node.js, at a pace that we haven’t seen before.
In 2019, there are many new features that are in the ‘Stage 3’ phase, which means that it’s very close to being finalized, and browsers/Node are getting support for these features now. If we want to use them for production, we can use something like Babel to transpile them to older versions of JavaScript so they can be used in older browsers like Internet Explorer if needed.
In this article, we look at private fields in classes, optional chaining, nullish coalescing operator, and BigInts.
Private Fields in Classes
One of the latest proposals is a way to add private variables in classes. We will use the #
sign to indicate that a class variable is a private variable. This way, we don’t need to use closures to hide private variables that we don’t want to expose to the outside world. For example, we can write a simple class to increment and decrement numbers like the following code:
class Counter {
#x = 0;
increment() {
this.#x++;
}
decrement() {
this.#x--;
}
getNum(){
return this.#x;
}
}
const c = new Counter();
c.increment();
c.increment();
c.decrement();
console.log(c.getNum());
We should get the value 1 from the console.log
in the last line of the code above. #x
is a private variable that can’t be accessed outside the class. So if we write:
console.log(c.#x);
We’ll get Uncaught SyntaxError: Private field '#x'
.
Private variables are a much-needed feature of JavaScript classes. This feature is now in the latest version of Chrome and Node.js v12.
Optional Chaining Operator
Currently, if we want to access a deeply nested property of an object, we have to check if the property in each nesting level is defined by using long boolean expressions. For example, we have to check each property is defined in each level until we can access the deeply nested property that we want, as in the following code:
const obj = {
prop1: {
prop2: {
prop3: {
prop4: {
prop5: 5
}
}
}
}
}
obj.prop1 &&
obj.prop1.prop2 &&
obj.prop1.prop2 &&
obj.prop1.prop2.prop3 &&
obj.prop1.prop2.prop3.prop4 &&
console.log(obj.prop1.prop2.prop3.prop4.prop5);
Fortunately, the code above has every property defined in every level that we want to access, so we will actually get the value 5. However, if we have a nested object inside an object that is undefined
or null
in any level, then our program will crash and the rest won’t run if we don’t do a check for it. This means that we have to check every level to make sure that it won’t crash when it runs into a undefined
or null
object.
With the optional chaining operator, we just need to use the ?.
to access nested objects. And if it bumps into a property that’s null
or undefined
, then it just returns undefined
. With optional chaining, we can instead write:
const obj = {
prop1: {
prop2: {
prop3: {
prop4: {
prop5: 5
}
}
}
}
}
console.log(obj?.prop1?.prop2?.prop3?.prop4?.prop5);
When our program runs into an undefined
or null
property, instead of crashing, we’ll just get back undefined
. Unfortunately, this hasn’t been natively implemented in any browser, so we have use the latest version Babel to use this feature.
Nullish Coalescing Operator
Another issue that comes from null
or undefined
values is that we have to make a default value to set a variable in case the variable that we want is null
or undefined
. For example, if we have:
const y = x || 500;
Then we have to make a default value in case x
is undefined
when it’s being set to y
by using the ||
operator. The problem with the ||
operator is that all falsy values like 0, false
, or an empty string will be overridden with the default value which we don’t always want to do.
To solve this, there was a proposal to create a “nullish” coalescing operator, which is denoted by ??
. With it, we only set the default value if the value if the first item is either null
or undefined
. For example, with the nullish coalescing operator, the expression above will become:
const y = x ?? 500;
For example, if we have the following code:
const x = null;
const y = x ?? 500;
console.log(y); // 500
const n = 0
const m = n ?? 9000;
console.log(m) // 0
y
will be assigned the value 500 since x
has the value null
. However, m
will be assigned the value 0
since n
is not null
or undfined
. If we had used ||
instead of ??
, then m
would have been assigned the value 9000 since 0 is falsy.
Unfortunately, this feature isn’t in any browser or Node.js yet so we have to use the latest version of Babel to use this feature.
BigInt
To represent integers larger than 2 to the 53rd power minus 1 in JavaScript, we can use the BigInt
object. It can be manipulated via normal operations like arithmetic operators — addition, subtraction, multiplication, division, remainder, and exponentiation. It can be constructed from numbers and hexadecimal or binary strings. Also, it supports bitwise operations like AND, OR, NOT, and XOR. The only bitwise operation that doesn’t work is the zero-fill right shift operator (>>>
) because BigInts are all signed.
Also the unary +
operator isn’t supported for adding Numbers and BitInts. These operations only are done when all the operands are BigInts. In JavaScript, a BigInt
is not the same as a normal number. It’s distinguished from a normal number by having an n
at the end of the number.
We can define a BigInt with the BigInt
factory function. It takes one argument that can be an integer number or a string representing a decimal integer, hexadecimal string, or binary string. BigInt cannot be used with the built-in Math object. Also, when converting between numbers and BigInt and vice versa, we have to be careful because the precision of BigInt might be lost when a BigInt is converted into a number.
To define a BigInt, we can write the following if we want to pass in a whole number:
const bigInt = BigInt(1);
console.log(bigInt);
Then it would log 1n
when we run the console.log
statement. If we want to pass in a string into the factory function instead, we can write:
const bigInt = BigInt('2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222');
console.log(bigInt);
We can also pass in a hexadecimal number string by using a string that starts with 0x
into the factory function:
const bigHex = BigInt("0x1fffffffffffff111111111");
console.log(bigHex);
The code above would log 618970019642690073311383825n
when we run console.log
on bigHex
.
Likewise, we can pass in a binary string with a string that starts with 0b
to get a BigInt:
const bigBin = BigInt("0b111111111111111000000000011111111111111111111111");
console.log(bigBin);
The code above would get us 281466395164671n
when we run console.log
on it. Passing in strings would be handy if the BigInt that we want to create is outside of the range that can be accepted by the number type.
We can also define a BigInt with a BigInt literal. We can just attach an n
character to the end of a whole number. For example, we can write:
const bigInt = 22222222222222222222222222222222n;
console.log(bigInt);
Then we get 22222222222222222222222222222222n
as the value of bigInt
when we log the value. We can do arithmetic operations like +
, -
, /
, *
, %
with BigInts like we do with numbers. All operands have to be BigInts if we want to do these operations with BigInts. However, if we get fractional results, the fractional parts will be truncated. BigInt is a big integer and it’s not for storing decimals. For example, in the examples below:
const expected = 8n / 2n;
console.log(expected) // 4n
const rounded = 9n / 2n;
console.log(rounded) // 4n
We get 4n
for expected
and rounded
. This is because the fractional part is removed from the BigInt.
Comparison operators can be applied to BigInts. The operands can be either BigInt or numbers. For example, we can compare 1n to 1:
1n === 1
The code above would evaluate to false
because BigInt and number aren’t the same type. But when we replace triple equals with double equals, like in the code below:
1n == 1
The statement above evaluate to true
because only the value is compared. Note that in both examples, we mixed BigInt operands with number operands. This is allowed for comparison operators. BigInts and numbers can be compared together with other operators as well, like in the following examples:
1n < 9
// true
9n > 1
// true
9 > 9n
// false
9n > 9
// false
9n >= 9
// true
Also, they can be mixed together in one array and sorted. For example, if we have the following code:
const mixedNums = [5n, 6, -120n, 12, 24, 0, 0n];
mixedNums.sort();
console.log(mixedNums)
This feature works with the latest version of Chrome and Node.js now, so we can start using it in our apps. We can use Babel to make it work with older browsers.
Conclusion
We can use the #
sign to indicate that a class variable is a private variable. This way, we don’t have to use closures to hide private variables that we don’t want to expose to the outside world. To solve problems with null
and undefined
values in objects, we have the optional chaining operator to access properties without checking that each level might be null
or undefined
. With the nullish coalescing operator, we can set default values for variables only for cases when it’s null
or undefined
. With BigInt objects, we can represent big numbers outside of the safe range of regular numbers in JavaScript and do the standard operations with them, except that fractional parts will be omitted from results.