Every Crazy Thing JavaScript Does
Every Crazy Thing JavaScript Does
Does
Coding Beauty
Introduction
JavaScript is a great language with a simple and readable syntax. It has a
large ecosystem and a great community. It is used by companies big and
small to create functional applications for a wide variety of platforms.
Still, we can’t deny that JavaScript has its quirkiness and weird parts. There
are subtle caveats you need to watch out for, to avoid seemingly invisible
bugs in places you least expect.
Hopefully, you’ll learn new and interesting things about the language by the
time you get to the end of this book.
An Array is NOT an Array
Programming languages follow the rules of logic, right? For instance, the law
of identity
A = A
2 == 2; // -> 4
5.5 == 5.5; // -> true
Explanation
Explanation
[] == ![];
Number([]) == Number(![]);
Number([]) == Number(false);
0 == 0;
true;
This is why you should always use strict inequality, to avoid surprises.
Explanation
The unary operator (+) has higher precedence than the addition operator (+)
so it is evaluated first in the expression. Applying the unary operator on a
string like a results in NaN, which is then coerced into a string for the
concatenation.
Explanation
By default, the sort() method sorts by converting the elements into strings,
then comparing their sequences of UTF-16 code unit values. Both 1 and 10
start with the same character, but 10 has a greater length, so is regarded as
the greater value. But 1 has a greater UTF-16 code unit than 4, so both 1 and
10 come before 4 in the sorted array.
To sort the numbers by their numeric value, we’ll need to pass a compareFn
to the sort() method:
And we all know 0.1 + 0.2 is 0.3. JavaScript doesn’t think so, however:
And if we multiplied by 2:
Explanation
In languages that use floating-point math, not all decimal numbers can be
exactly represented. As it is a binary system, only fractions whose
denominator is a power of 2 can be expressed accurately. Values like 0.1
and 0.2 (1/10 and 1/5) would have repeating decimals in their internal
representation. When you perform math on these repeating decimals, there
will leftovers which carry over in the conversion of the number from the
computer’s base-2 binary representation into a more readable base-10
representation.
Object.is() vs ===
Object.is() checks if two values have the same value or not. It works like
the === operator, except for some weird cases:
Explanation
In JavaScript, NaN and NaN are the same value, but they’re not strictly equal.
In the same way, -0 and 0 are strictly equal, but they’re not the same value.
Plus and Minus
Testing the minus operator (-) here:
'6' - 2 // -> 4
JavaScript understands that the string '6' can be the number 6. However, if
we try something similar with the plus operator (+):
Explanation
When we try to use the + operator String and a Number, JavaScript sees it
as a concatenation operator, instead of a numeric addition. So it converts the
Number (2 here) into a `String`` and performs the concatenation.
Number('6') + 2 // -> 8
Strange parseInt() Behaviour
Sometimes parseInt() behaves in ways you might not expect:
Explanation
parseInt() parses each character of the string one after the other until it
meets a character that doesn’t exist in the radix. In the first call to
parseInt(), the default base of 10 is used and the first character (f) does
not exist in this base, so it returns NaN right away. In the second call, the base
is 16, so f is parsed to 15, before the function stops when it hits the *
character.
// ...
parseInt('Infinity', 10); // -> NaN
// ...
parseInt('Infinity', 18); // -> NaN
parseInt('Infinity', 19); // -> 18
// ...
parseInt('Infinity', 23); // -> 18
parseInt('Infinity', 24); // -> 151176378
// ...
parseInt('Infinity', 29); // -> 385849803
parseInt('Infinity', 30); // -> 13693557269
// ...
parseInt('Infinity', 34); // -> 28872273981
parseInt('Infinity', 35); // -> 1201203301724
parseInt('Infinity', 36); // -> 1461559270678
parseInt('Infinity', 37); // -> NaN
Explanation
null is coerced to the string 'null' before the parsing. This string has no
characters that existing from base 0 to base 23, so parseInt() returns NaN
for them. Starting from base 24, the letter n is used in the numeral system and
it is parsed to 23 in this base. Starting from base 31, the letter u is used in the
numeral system and the entire string can be parsed. None of the characters of
'null' exist in base 37, so parseInt() returns NaN for this base.
parseInt('06');
parseInt('08'); // ES5+
parseInt('08'); // Before ES5
Explanation
If the input string starts with 0, the base might default to 8 or 10, depending
on the JavaScript version. ECMAScript 5 specifies that base 10 should be
used, but not all browsers support it yet. This is why you should always
explicitly set a base when using parseInt().
This is different from other methods like the Number() constructor, which
uses the valueOf() method to convert the string:
parseInt() can also behave wierdly when parsing floating point values:
parseInt(0.000001); // -> 0
parseInt(0.0000001); // -> 1
parseInt(1 / 1999999); // -> 5
Explanation
parseInt() takes a string argument and returns an integer of the specified
base. parseInt() also stops parsing once it meets a non-digit character in
the string. So 0.000001 is coerced to the string '0.000001', and
parseInt() stops parsing once it meets the period, leaving only the string
'0', which gets converted to the number 0. 0.0000001 is interpreted as 1e-
7, which is the equivalent scientific notation . parseInt() stops parsing the
string '1e-7' at the non-digit character 'e', leaving only the string '1' to be
converted. A similar thing happens for the last statement, where 1 /
1999999 is interpreted as 5.00000250000125e-7 which results in 5 when
passed to parseInt().
parseInt() and map()
Using the Array map() method and parseInt() can produce interesting
results:
Explanation
parseInt(string, radix);
To ensure parseInt() only gets a single argument and properly parses each
string to a number:
function func() {
var num = 10;
if (true) {
var num = 20;
console.log(num); // 20
}
console.log(num); // 20
}
func();
Explanation
Both the var and let keywords in JavaScript are used to declare a variable,
but there is an important difference between them that you should be aware
of. A variable declared with var exists in the entire scope of the innermost
function where it is declared, while one declared with let exists only in the
innermost block where it is declared.
function func() {
{
var num = 10;
}
console.log(num); // 10
}
func();
But with let, it doesn’t exist outside the innermost enclosing block:
function func() {
{
let num = 10;
}
console.log(num); // ReferenceError: num is not defined
}
func();
So creating a new num variable in the body of the if statement with var
unexpectedly modified the num variable that we declared in the outer scope.
Using let prevents this sort of issue:
function func() {
let num = 10;
if (true) {
let num = 20;
console.log(num); // 20
}
console.log(num); // 10
}
func();
Closures in Loops
Creating closures in loops that access var variables can produce strange
results.
Example
var funcs = [];
// create 3 functions
for (var i = 0; i < 3; i++) {
// store them in funcs
funcs[i] = function () {
// each logs a value
console.log('My value: ' + i);
};
}
// Run each function to test
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Output
My value: 3
My value: 3
My value: 3
Explanation
In JavaScript, a closure “remembers” the value of any variable existing
outside the closure, even if the variable changes:
var num = 1;
var closure = function () {
console.log(num);
};
num = 3;
closure(); // 3
Since we used var to declare the loop counter (i), it exists outside the for
loop scope, and all the closures added to the funcs array are each bound to
the same i variable.
We can use let in place of var in the for loop to make the code work as
expected:
Example
for (let i = 0; i < 3; i++) {
// store them in funcs
funcs[i] = function () {
// each logs a value
console.log('My value: ' + i);
};
}
Output
My value: 0
My value: 1
My value: 2
Insane Writing
JavaScript offers an interesting way to print letters and numbers:
Explanation
This is because arrays are truthy and the ! operator coerces the empty array
to the boolean false. We can convert this Boolean to a string by adding []
to it:
This happens because the right operand is coerced to a string due to some
internal function calls (binary + operator -> ToPrimitive ->
[[DefaultValue]].)
Also:
+!![] // -> 1
Because the unary plus operator (+) coerces the Boolean to a Number (true
to 1).
By combining a few JavaScript symbols in different ways, we can generate
strings to output almost any word without explicitly using string literals in the
code.
+![] // -> 0
+!![] // -> 1
!![] // -> true
![] // -> false
[][[]] // -> undefined
+!![] / +![] // -> Infinity
[] + {} // -> '[object Object]'
+{} // -> NaN
('false' + 'undefined')[10] +
'undefined'[1] +
'false'[3] +
'false'[1]
+ 'undefined'[1] +
'[object Object]'[11]
'i' +
'n' +
's' +
'a' +
'n' +
'e'
'insane'
The null Object
typeof []; // -> 'object'
typeof null; // -> 'object'
// however
null instanceof Object; // false
Explanation
Tip
You can check for the type of an object with the toString method:
Explanation
Explanation
Explanation
// c is a string
c; // -> 'constructor'
Object.prototype.constructor at MDN
19.1.3.1 Object.prototype.constructor from the specification
undefined and Number()
If we call the Number() constructor without passing any arguments, we’ll get
0. In JavaScript, undefined is passed as an argument to function parameters
when no argument is explicitly specified, so you might expect that Number()
without arguments takes undefined as a value of its parameter. In reality,
passing undefined to Number() results in NaN.
Number(); // -> 0
Number(undefined); // -> NaN
Explanation
Explanation
[1, 2, 3] + [4, 5, 6]
'1,2,3' + '4,5,6'
'1,2,34,5,6'
Valid HTML Comments
Did you know that HTML comments (starting with <!--) are valid comments
in JavaScript? Your IDE might complain, but they work:
// valid comment
<!-- valid comment too
Explanation
Explanation
Explanation
The . character can mean different things in JavaScript. It can be seen as the
member operator, or a decimal, depending on its placement.
To get more helpful resources and stay updated with our latest tips and
tutorials, become a subscriber by signing up here.