Try to write one third as a decimal. You get 0.333, and the threes never end, so at some point you have to stop and round. Computers count in ones and zeros, and for them 0.1 is like one third. It never ends, so the computer stores a number that is very slightly off.
The curse
console.log(0.1 + 0.2); // prints: 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // prints: false
The answer is off by a tiny amount, down in the 17th decimal place. That is enough to make === say false, because === checks for exactly the same value.
Why it happens
JavaScript stores every number in a format called 64-bit floating point (the IEEE 754 standard). It keeps the number in binary, with room for about 15 to 17 significant digits.
In base ten, a fraction can be written exactly only if its bottom number is built from 2s and 5s. That is why 1/4 is 0.25 but 1/3 runs on forever. In binary, only 2s work. So 1/2 and 1/4 are exact, but 1/10 has a 5 in it and never ends. The computer cuts it off and stores the closest number it can.
console.log(0.1.toFixed(20)); // prints: 0.10000000000000000555
console.log(0.5 + 0.25 === 0.75); // prints: true
Most of the time the tiny error is hidden when the number is printed, so 0.1 still shows as 0.1. With 0.1 + 0.2, the two small errors add up and land on a different stored number than 0.3, so you get to see it.
This is not a JavaScript bug. Python, Java, C and almost every other language give the same answer, because they use the same number format.
How to compare decimals
Instead of asking whether two decimals are exactly equal, check whether they are close enough. Subtract them, drop the minus sign with Math.abs, and see if what is left is smaller than a tiny number.
const total = 0.1 + 0.2;
console.log(Math.abs(total - 0.3) < Number.EPSILON); // prints: true
Number.EPSILON is built into JavaScript. It is the gap between 1 and the next number JavaScript can store, about 0.0000000000000002. That makes it a good "close enough" for small numbers like these.
For bigger numbers the gaps between stored values get bigger too, so Number.EPSILON becomes too strict. In that case pick a tolerance that fits your data, like 0.001 for measurements in meters.
Money and showing numbers
For money, count whole cents instead of dollars. Whole numbers are stored exactly (up to about nine quadrillion), so they always add up correctly.
const candyCents = 10;
const gumCents = 20;
console.log(candyCents + gumCents); // prints: 30
console.log((0.1 + 0.2).toFixed(2)); // prints: 0.30
console.log(typeof (0.1 + 0.2).toFixed(2)); // prints: string
To show a tidy number on screen, round it with toFixed. Be careful, because it gives you back text. Use it only for display, at the very end. If you keep calculating with the result, the plus sign will start gluing strings.
Rule of thumb
Don't compare decimals with ===, check whether they are close enough instead.
Turning dollars into cents with price * 100 and trusting the result. 19.99 * 100 is 1998.9999999999998, so wrap it in Math.round(price * 100) to get a clean 1999.
Check yourself
Computers store numbers in binary, where 0.1 never ends, so it gets rounded. The tiny rounding errors add up to 0.30000000000000004.