What the f*ck JavaScript?
A list of funny and tricky JavaScript examples
JavaScript is a great language. It has a simple syntax, large ecosystem and, what is most important, a great community.
At the same time, we all know that JavaScript is quite a funny language with tricky parts. Some of them can quickly turn our everyday job into hell, and some of them can make us laugh out loud.
The original idea for WTFJS belongs to Brian Leroux. This list is highly inspired by his talk “WTFJS” at dotJS 2012:
Node Packaged Manuscript
You can install this handbook using npm
. Just run:
$ npm install -g wtfjs
You should be able to run wtfjs
at the command line now. This will open the manual in your selected $PAGER
. Otherwise, you may continue reading on here.
The source is available here: https://github.com/denysdovhan/wtfjs
Translations
Currently, there are these translations of wtfjs:
Help translating to your language
Note: Translations are maintained by their translators. They may not contain every example, and existing examples may be outdated.
Table of Contents
- 💪🏻 Motivation
- ✍🏻 Notation
-
👀 Examples[]
is equal![]
true
is not equal![]
, but not equal[]
too- true is false
- baNaNa
NaN
is not aNaN
Object.is()
and===
weird cases- It's a fail
[]
is truthy, but nottrue
null
is falsy, but notfalse
document.all
is an object, but it is undefined- Minimal value is greater than zero
- function is not a function
- Adding arrays
- Trailing commas in array
- Array equality is a monster
undefined
andNumber
parseInt
is a bad guy- Math with
true
andfalse
- HTML comments are valid in JavaScript
NaN
isnota number[]
andnull
are objects- Magically increasing numbers
- Precision of
0.1 + 0.2
- Patching numbers
- Comparison of three numbers
- Funny math
- Addition of RegExps
- Strings aren't instances of
String
- Calling functions with backticks
- Call call call
- A
constructor
property - Object as a key of object's property
- Accessing prototypes with
__proto__
`${{Object}}`
- Destructuring with default values
- Dots and spreading
- Labels
- Nested labels
- Insidious
try..catch
- Is this multiple inheritance?
- A generator which yields itself
- A class of class
- Non-coercible objects
- Tricky arrow functions
- Arrow functions can not be a constructor
arguments
and arrow functions- Tricky return
- Chaining assignments on object
- Accessing object properties with arrays
- Null and Relational Operators
Number.toFixed()
display different numbersMath.max()
less thanMath.min()
- Comparing
null
to0
- Same variable redeclaration
- Default behavior Array.prototype.sort()
- resolve() won't return Promise instance
{}{}
is undefinedmin
is greater thanmax
arguments
binding- An
alert
from hell - An infinite timeout
- A
setTimeout
object - Double dot
- Extra Newness
- Why you should use semicolons
- Split a string by a space
- A stringified string
- Non-strict comparison of a number to
true
-
📚 Other resources -
🤝 Supporting -
🎓 License
💪🏻 Motivation
Just for fun
— “Just for Fun: The Story of an Accidental Revolutionary”, Linus Torvalds
The primary goal of this list is to collect some crazy examples and explain how they work, if possible. Just because it's fun to learn something that we didn't know before.
If you are a beginner, you can use these notes to get a deeper dive into JavaScript. I hope these notes will motivate you to spend more time reading the specification.
If you are a professional developer, you can consider these examples as a great reference for all of the quirks and unexpected edges of our beloved JavaScript.
In any case, just read this. You're probably going to find something new.
⚠️ Note: If you enjoy reading this document, please, consider supporting the author of this collection.
✍🏻 Notation
// ->
is used to show the result of an expression. For example:
1 + 1; // -> 2
// >
means the result of console.log
or another output. For example:
console.log("hello, world!"); // > hello, world!
//
is just a comment used for explanations. Example:
// Assigning a function to foo constant
const foo = function() {};
👀
Examples
[]
is equal ![]
Array is equal not array:
[] == ![]; // -> true
💡
Explanation:
The abstract equality operator converts both sides to numbers to compare them, and both sides become the number 0
for different reasons. Arrays are truthy, so on the right, the opposite of a truthy value is false
, which is then coerced to 0
. On the left, however, an empty array is coerced to a number without becoming a boolean first, and empty arrays are coerced to 0
, despite being truthy.
Here is how this expression simplifies:
+[] == +![];
0 == +false;
0 == 0;
true;
See also []
is truthy, but not true
.
true
is not equal ![]
, but not equal []
too
Array is not equal true
, but not Array is not equal true
too; Array is equal false
, not Array is equal false
too:
true == []; // -> false
true == ![]; // -> false
false == []; // -> true
false == ![]; // -> true
💡
Explanation:
true == []; // -> false
true == ![]; // -> false
// According to the specification
true == []; // -> false
toNumber(true); // -> 1
toNumber([]); // -> 0
1 == 0; // -> false
true == ![]; // -> false
![]; // -> false
true == false; // -> false
false == []; // -> true
false == ![]; // -> true
// According to the specification
false == []; // -> true
toNumber(false); // -> 0
toNumber([]); // -> 0
0 == 0; // -> true
false == ![]; // -> true
![]; // -> false
false == false; // -> true
true is false
!!"false" == !!"true"; // -> true
!!"false" === !!"true"; // -> true
💡
Explanation:
Consider this step-by-step:
// true is 'truthy' and represented by value 1 (number), 'true' in string form is NaN.
true == "true"; // -> false
false == "false"; // -> false
// 'false' is not the empty string, so it's a truthy value
!!"false"; // -> true
!!"true"; // -> true
baNaNa
"b" + "a" + +"a" + "a"; // -> 'baNaNa'
This is an old-school joke in JavaScript, but remastered. Here's the original one:
"foo" + +"bar"; // -> 'fooNaN'
💡
Explanation:
The expression is evaluated as 'foo' + (+'bar')
, which converts 'bar'
to not a number.
NaN
is not a NaN
NaN === NaN; // -> false
💡
Explanation:
The specification strictly defines the logic behind this behavior:
- If
Type(x)
is different fromType(y)
, return false.- If
Type(x)
is Number, then
- If
x
is NaN, return false.- If
y
is NaN, return false.- … … …
Following the definition of NaN
from the IEEE:
Four mutually exclusive relations are possible: less than, equal, greater than, and unordered. The last case arises when at least one operand is NaN. Every NaN shall compare unordered with everything, including itself.
— “What is the rationale for all comparisons returning false for IEEE754 NaN values?” at StackOverflow
Object.is()
and ===
weird cases
Object.is()
determines if two values have the same value or not. It works similar to the ===
operator but there are a few weird cases:
Object.is(NaN, NaN); // -> true
NaN === NaN; // -> false
Object.is(-0, 0); // -> false
-0 === 0; // -> true
Object.is(NaN, 0 / 0); // -> true
NaN === 0 / 0; // -> false
💡
Explanation:
In JavaScript lingo, NaN
and NaN
are the same value but they're not strictly equal. NaN === NaN
being false is apparently due to historical reasons so it would probably be better to accept it as it is.
Similarly, -0
and 0
are strictly equal, but they're not the same value.
For more details about NaN === NaN
, see the above case.
It's a fail
You would not believe, but …
(![] + [])[+[]] +
(![] + [])[+!+[]] +
([![]] + [][[]])[+!+[] + [+[]]] +
(![] + [])[!+[] + !+[]];
// -> 'fail'
💡
Explanation:
By breaking that mass of symbols into pieces, we notice that the following pattern occurs often:
![] + []; // -> 'false'
![]; // -> false
So we try adding []
to false
. But due to a number of internal function calls (binary + Operator
-> ToPrimitive
-> [[DefaultValue]]
) we end up converting the right operand to a string:
![] + [].toString(); // 'false'
Thinking of a string as an array we can access its first character via [0]
:
"false"[0]; // -> 'f'
The rest is obvious, but the i
is tricky. The i
in fail
is grabbed by generating the string 'falseundefined'
and grabbing the element on index ['10']
.
More examples:
+![] // -> 0
+!![] // -> 1
!![] // -> true
![] // -> false
[][[]] // -> undefined
+!![] / +![] // -> Infinity
[] + {} // -> "[object Object]"
+{} // -> NaN
- Brainfuck beware: JavaScript is after you!
- Writing a sentence without using the Alphabet — generate any phrase using JavaScript
[]
is truthy, but not true
An array is a truthy value, however, it's not equal to true
.
!![] // -> true
[] == true // -> false
💡
Explanation:
Here are links to the corresponding sections in the ECMA-262 specification:
null
is falsy, but not false
Despite the fact that null
is a falsy value, it's not equal to false
.
!!null; // -> false
null == false; // -> false
At the same time, other falsy values, like 0
or ''
are equal to false
.
0 == false; // -> true
"" == false; // -> true
💡
Explanation:
The explanation is the same as for previous example. Here's the corresponding link:
document.all
is an object, but it is undefined
⚠️ This is part of the Browser API and won't work in a Node.js environment⚠️
Despite the fact that document.all
is an array-like object and it gives access to the DOM nodes in the page, it responds to the typeof
function as undefined
.
document.all instanceof Object; // -> true
typeof document.all; // -> 'undefined'
At the same time, document.all
is not equal to undefined
.
document.all === undefined; // -> false
document.all === null; // -> false
But at the same time:
document.all == null; // -> true
💡
Explanation:
document.all
used to be a way to access DOM elements, in particular with old versions of IE. While it has never been a standard it was broadly used in the old age JS code. When the standard progressed with new APIs (such asdocument.getElementById
) this API call became obsolete and the standard committee had to decide what to do with it. Because of its broad use they decided to keep the API but introduce a willful violation of the JavaScript specification. The reason why it responds tofalse
when using the Strict Equality Comparison withundefined
whiletrue
when using the Abstract Equality Comparison is due to the willful violation of the specification that explicitly allows that.— “Obsolete features - document.all” at WhatWG - HTML spec — “Chapter 4 - ToBoolean - Falsy values” at YDKJS - Types & Grammar
Minimal value is greater than zero
Number.MIN_VALUE
is the smallest number, which is greater than zero:
Number.MIN_VALUE > 0; // -> true
💡
Explanation:
Number.MIN_VALUE
is5e-324
, i.e. the smallest positive number that can be represented within float precision, i.e. that's as close as you can get to zero. It defines the best resolution that floats can give you.Now the overall smallest value is
Number.NEGATIVE_INFINITY
although it's not really numeric in a strict sense.— “Why is
0
less thanNumber.MIN_VALUE
in JavaScript?” at StackOverflow
function is not a function
⚠️ A bug present in V8 v5.5 or lower (Node.js <=7)⚠️
All of you know about the annoying undefined is not a function, but what about this?
// Declare a class which extends null
class Foo extends null {}
// -> [Function: Foo]
new Foo() instanceof null;
// > TypeError: function is not a function
// > at … … …
💡
Explanation:
This is not a part of the specification. It's just a bug that has now been fixed, so there shouldn't be a problem with it in the future.
Super constructor null of Foo is not a constructor
It's continuation of story with previous bug in modern environment (tested with Chrome 71 and Node.js v11.8.0).
class Foo extends null {}
new Foo() instanceof null;
// > TypeError: Super constructor null of Foo is not a constructor
💡
Explanation:
This is not a bug because:
Object.getPrototypeOf(Foo.prototype); // -> null
If the class has no constructor the call from prototype chain. But in the parent has no constructor. Just in case, I’ll clarify that null
is an object:
typeof null === "object";
Therefore, you can inherit from it (although in the world of the OOP for such terms would have beaten me). So you can't call the null constructor. If you change this code:
class Foo extends null {
constructor() {
console.log("something");
}
}
You see the error:
ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
And if you add super
:
class Foo extends null {
constructor() {
console.log(111);
super();
}
}
JS throws an error:
TypeError: Super constructor null of Foo is not a constructor
Adding arrays
What if you try to add two arrays?
[1, 2, 3] + [4, 5, 6]; // -> '1,2,34,5,6'
💡
Explanation:
The concatenation happens. Step-by-step, it looks like this:
[1, 2, 3] +
[4, 5, 6][
// call toString()
(1, 2, 3)
].toString() +
[4, 5, 6].toString();
// concatenation
"1,2,3" + "4,5,6";
// ->
("1,2,34,5,6");
Trailing commas in array
You've created an array with 4 empty elements. Despite all, you'll get an array with three elements, because of trailing commas:
let a = [, , ,];
a.length; // -> 3
a.toString(); // -> ',,'
💡
Explanation:
Trailing commas (sometimes called "final commas") can be useful when adding new elements, parameters, or properties to JavaScript code. If you want to add a new property, you can simply add a new line without modifying the previously last line if that line already uses a trailing comma. This makes version-control diffs cleaner and editing code might be less troublesome.
— Trailing commas at MDN
Array equality is a monster
Array equality is a monster in JS, as you can see below:
[] == '' // -> true
[] == 0 // -> true
[''] == '' // -> true
[0] == 0 // -> true
[0] == '' // -> false
[''] == 0 // -> true
[null] == '' // true
[null] == 0 // true
[undefined] == '' // true
[undefined] == 0 // true
[[]] == 0 // true
[[]] == '' // true
[[[[[[]]]]]] == '' // true
[[[[[[]]]]]] == 0 // true
[[[[[[ null ]]]]]] == 0 // true
[[[[[[ null ]]]]]] == '' // true
[[[[[[ undefined ]]]]]] == 0 // true
[[[[[[ undefined ]]]]]] == '' // true
💡
Explanation:
You should watch very carefully for the above examples! The behaviour is described in section 7.2.15 Abstract Equality Comparison of the specification.
undefined
and Number
If we don't pass any arguments into the Number
constructor, we'll get 0
. The value undefined
is assigned to formal arguments when there are no actual arguments, so you might expect that Number
without arguments takes undefined
as a value of its parameter. However, when we pass undefined
, we will get NaN
.
Number(); // -> 0
Number(undefined); // -> NaN
💡
Explanation:
According to the specification:
- If no arguments were passed to this function's invocation, let
n
be+0
. - Else, let
n
be ?ToNumber(value)
. - In case of
undefined
,ToNumber(undefined)
should returnNaN
.
Here's the corresponding section:
parseInt
is a bad guy
parseInt
is famous by its quirks:
parseInt("f*ck"); // -> NaN
parseInt("f*ck", 16); // -> 15
parseInt
will continue parsing character-by-character until it hits a character it doesn't know. The f
in 'f*ck'
is the hexadecimal digit 15
.
Parsing Infinity
to integer is something…
//
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
Be careful with parsing null
too:
parseInt(null, 24); // -> 23
It's converting
null
to the string"null"
and trying to convert it. For radixes 0 through 23, there are no numerals it can convert, so it returns NaN. At 24,"n"
, the 14th letter, is added to the numeral system. At 31,"u"
, the 21st letter, is added and the entire string can be decoded. At 37 on there is no longer any valid numeral set that can be generated andNaN
is returned.— “parseInt(null, 24) === 23… wait, what?” at StackOverflow
Don't forget about octals:
parseInt("06"); // 6
parseInt("08"); // 8 if support ECMAScript 5
parseInt("08"); // 0 if not support ECMAScript 5
parseInt
.
parseInt
always convert input to string:
parseInt({ toString: () => 2, valueOf: () => 1 }); // -> 2
Number({ toString: () => 2, valueOf: () => 1 }); // -> 1
Be careful while parsing floating point values
parseInt(0.000001); // -> 0
parseInt(0.0000001); // -> 1
parseInt(1 / 1999999); // -> 5
ParseInt
takes a string argument and returns an integer of the specified radix. ParseInt
also strips anything after and including the first non-digit in the string parameter. 0.000001
is converted to a string "0.000001"
and the parseInt
returns 0
. When 0.0000001
is converted to a string it is treated as "1e-7"
and hence parseInt
returns 1
. 1/1999999
is interpreted as 5.00000250000125e-7
and parseInt
returns 5
.
true
and false
Math with Let's do some math:
true + true; // -> 2
(true + true) * (true + true) - true; // -> 3
Hmmm…
💡
Explanation:
We can coerce values to numbers with the Number
constructor. It's quite obvious that true
will be coerced to 1
:
Number(true); // -> 1
The unary plus operator attempts to convert its value into a number. It can convert string representations of integers and floats, as well as the non-string values true
, false
, and null
. If it cannot parse a particular value, it will evaluate to NaN
. That means we can coerce true
to 1
easier:
+true; // -> 1
When you're performing addition or multiplication, the ToNumber
method is invoked. According to the specification, this method returns:
If
argument
is true, return 1. Ifargument
is false, return +0.
That's why we can add boolean values as regular numbers and get correct results.
Corresponding sections:
HTML comments are valid in JavaScript
You will be impressed, but