6 JavaScript WTFs and what to learn from them
Andrei Bumbu
Posted on July 29, 2019
Hi! I'm sure that in your JavaScript developer journey you saw at least once (per day) ‘undefined’ is not a function or you asked yourself why the type of the NaN is actually a number. Well, sometimes JavaScript wants to put you to the pressure test.
In this article, you will be part of an adventure in the funny (and dark) side of this beautiful programming language. Let's start!
1. Min > Max
Math.min() > Math.max() //true
Explanation:
Ok, so first let's define a couple of things.
Math
is a built-in object that has properties and methods for mathematical constants and functions. Not a function object.- The static function
Math.max()
returns the largest-valued number passed into it, orNaN
if any parameter isn't a number and can't be converted into one.
Perfect, until now we know what the Math object represents in JavaScript and what the .max() static function does. Similarly, the .min() function will do the opposite operation. Until now our instinct may say that Math.max() should return the Number.MAX_VALUE if no parameters are provided.
However, that assumption would be erroneous. Here's why. Imagine that you will have to implement a function that finds the maximum value from an array. That's easy! The simplest way to do it is to iterate through the entire array, compare the elements and store the maximum value. Here's the twist! That variable where you store the maximum value should be initialized with a very, very small value, the smallest.
You might think by now that, ok, the smallest value in Javascript is Number.MIN_VALUE (5e-324) and you are right. But, JavaScript has something prepared for you in this particular case and that is Infinity.
The global
Infinity
property is a numeric value representing infinity.
And finally, full description of .max() function:
Math.max() returns the largest of the given numbers. If any one or more of the parameters cannot be converted into a number,
NaN
is returned. The result is-Infinity
if no parameters are provided.
Math.min() > Math.max() -> Infinity > -Infinity //true
Takeaways:
- What is Math object
- How min() and max() functions behave
- Infinity object in JavaScript
2. 0.1 + 0.2 = ?
Well, that's too easy. 0.1 + 0.2 = 0.3, right? Not in JavaScript! (or JAVA, or C++, or C#, or..you got the point)
0.1 + 0.2 === 0.3 //false
Explanation:
How is this possible? Before you reconsider all the basic math that you learned, let me introduce Floating Point Math.
Computers can only natively store integers, so they need some way of representing decimal numbers. This representation comes with some degree of inaccuracy.
This subject's a complex one and needs some serious number of hours invested in it. However, I will try to simplify it for this particular situation.
In the case of base 10 system, the only fractions that can be expressed cleanly are the ones with prime factor as a base (½, ¼, 1⁄5, etc.). In comparison 1⁄3 has repeating decimals (0,33333..). Now if we take this information and apply it for 2 base system, the clean fractions are ½, ¼ and 1⁄8, while 1⁄5 and 1⁄10 have repeating decimals. That's causing some leftovers in this example.
0.1 + 0.2 === 0.30000000000000004 //true
When you do the math on these repeating decimals, you end up with leftovers which carry over when you convert the computer's base 2 (binary) number into a more human-readable base 10 number.
Takeaways:
- Floating Point Math overview
- This concept applies to most of the programming languages
3. baNaNa
Good, after that hard mathematical problem, let's do something fun!
"b" + "a" + +"a" + "a" -> baNaNa
Explanation:
Different from the other 2 WTFs, this one is a little bit simpler. That's because you have 75% of the problem solved. Now we have just one tiny aspect to clarify: what + +"a" will return.
JavaScript syntax is valid because the second + is not the addition operator, but a unary operator.
The unary + operator converts its operand to Number type. Any unsupported values will be evaluated to
NaN
.
That was effortless! So our expression will l as below, because "a" can't be converted to a Number.
"b" + "a" + NaN + "a" -> baNaNa
In order to conclude we should confirm one more piece of the puzzle. What String + String + NaN + String will return? How the addition operator will behave?
The addition operator either performs string concatenation or numeric addition.
So, there are two types of addition that can occur, string concatenation or numeric addition, in this particular order. The way the algorithm work is the following:
Transform the operands into primitives using ToPrimitive() function.
If one of the operands is a String, then transform the other one into a String and perform string concatenation. Otherwise, convert both of them to Number and execute numeric addition.
"b" + "a"-> "ba"
"ba" + NaN -> "ba" + "NaN" -> "baNaN"
"baNaN" + "a" -> "baNaNa"
Takeaways:
- What is Unary operator
- Addition operator algorithm
- ToPrimitive() function and one use case for it
4. Initialization before the declaration?
Take this code for example:
message = "don't forget to hit the heart button if you liked it.";
console.log(promoteArticle("Stranger"));
function promoteArticle(name) {
return `${name}, ${message}`;
};
var message;
What will be prompted in the console? A ReferenceError that message is not defined? Or maybe the string "Stranger, undefined". No, it must be a TypeError, promoteArticle is not a function.
Fortunately for us, the output will be exactly what we wanted: "Stranger, don't forget to hit the heart button if you linked it". But why? The "Hoisting" (buzzword) JavaScript behavior is responsible for that.
Hoisting is JavaScript's default behavior of
moving declarations to the top
.
Note: This is available only for variables defined with the var keyword and declared functions.
Using this piece of information we can claim that our code will look like this after compilation:
function promoteArticle(name) {
return `${name}, ${message}`;
};
var message;
message = "don't forget to hit the heart button if you liked it.";
console.log(promoteArticle("Stranger"));
Let's take it step by step. The promoteArticle() function is on the top because function declarations are the first elements moved to the top, followed by var variable declaration.
Also, no error is thrown and the message has the right value is because by the time the function is invoked, the variable was both declared and initialized.
Just to make sure I didn't cause any kind of confusion, I will mention the difference between declared functions and expression functions. Below is an example containing both types.
function declaredPromoteArticle(name) {
return `${name}, ${message}`;
};
var expressionPromoteArticle = function(name) {
return `${name}, ${message}`;
}
And after compile:
function declaredPromoteArticle(name) {
return `${name}, ${message}`;
};
var expressionPromoteArticle; // just the variable definition was hoisted
expressionPromoteArticle = function(name) {
return `${name}, ${message}`;
}
Takeaways:
- What is Hoisting
- Function declaration vs function expression
5. typeof NaN == 'number'
This one might appear strange especially because of the lexical side, "Not a number is a number", but it will make sense in a second. First, let's inspect the definition:
The global
NaN
property is a value representing Not-A-Number.
A simple and clear definition for NaN, but we can find the trick in the 'global' word. Contrary to our first instinct, NaN is not a keyword (like null, if, var, etc.), but a global property. What global object may include this property? Yes, you guessed it, is the Number object.
typeof NaN == 'number' -> typeof Number.NaN == 'number' //true
Number.MIN_VALUE
The smallest positive representable number - that is, the positive number closest to zero (without actually being zero).
Number.NaN
Special "not a number" value.
Why I also extracted the MIN_VALUE property you may ask. It's because it will be clearer why the JavaScript compiler doesn't know any difference between MIN_VALUE and NaN property, and therefore both types are numbers.
Takeaways:
- NaN it's not a keyword, but a property
- How type of operator behaves in this case
6. Array.prototype.sort()
The subject of the last WTF is the behavior of the sort() method, without any parameter sent.
[32, 3, 6].sort() //[3, 32, 6]
Ok, that not work how we expected. Why the values are in that particular order? Let's take more examples and be adventurous.
[32, 3, true, 6].sort() //[3, 32, 6, true]
[32, 3, true, 6, NaN].sort() //[3, 32, 6, NaN, true]
[32, 3, true, 6, NaN, undefined].sort() //[3, 32, 6, NaN, true, undefined]
Got it? Yup, the default algorithm casts each value into a string and then sort them accordingly.
In order to reach the expected result, sort() method will need a compare function as a parameter. That function receives two parameters and return a number which describes the relation between them.
The notation a < b means comparefn(a, b) < 0; a = b means comparefn(a, b) = 0 (of either sign); and a > b means comparefn(a, b) > 0.
Below it's an example using an array of users. The sort algorithm is based on the age property of each user.
let users = [
{
name: "Andrei",
age: 24
},
{
name: "Denisa",
age: 23
}];
users.sort((first, second) => first.age - second.age);
//[ { name: 'Denisa', age: 23 }, { name: 'Andrei', age: 24 } ]
Takeaways:
- Array.prototype.sort() default behavior
- How to implement a specific sort mechanism
Bonus: NaN
is not a NaN
Surprise, there is more!
NaN === NaN //false
This one refers to the Strict Equality Comparison and its implementation.
- If Type(x) is different from Type(y), return false.
- If Type(x) is Number, then
- If x is
NaN
, return false.- If y is
NaN
, return false. ...- Return SameValueNonNumber(x, y).
As we know, the NaN type is number so the second if the condition is matched. After that, if any of the operands is NaN, false is returned.
Takeaways:
- The first part of Strict Equality Comparison implementation
- Last part of that algorithm uses another one named SameValueNonNumber
Finally, we are done. You might think that those WTFs are childish (and you will be right from some of them), but they may hide small bugs (with big impact) and waste a lot of your time and energy.
Also, the habit to search in the official documentation when something seems fishy in your code and learn how the compiler "thinks" can really improve your skills.
Note: This is my first attempt in writing technical articles. Please let me any kind of feedback in the comments and also what topics are you interested in. Happy coding!
Posted on July 29, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.