Need help with wtfjs?
Click the “chat” button below for chat support from the developer who created it, or find similar developers for support.

About the developer

denysdovhan
20.1K Stars 1.3K Forks Do What The F*ck You Want To Public License 291 Commits 50 Opened issues

Description

🤪 A list of funny and tricky JavaScript examples

Services available

!
?

Need anything else?

Contributors list

No Data

What the f*ck JavaScript?

WTFPL 2.0 NPM version

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:

dotJS 2012 - Brian Leroux - WTFJS

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:

Request another translation

Table of Contents

💪🏻 Motivation

Just for fun

[“Just for Fun: The Story of an Accidental Revolutionary”](https://en.wikipedia.org/wiki/JustforFun), 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.

✍🏻 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:

  1. If
    Type(x)
    is different from
    Type(y)
    , return false.
  2. If
    Type(x)
    is Number, then
    1. If
      x
      is NaN, return false.
    2. If
      y
      is NaN, return false.
    3. … … …

7.2.14 Strict Equality Comparison

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

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']

[]
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 as
document.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 to
false
when using the Strict Equality Comparison with
undefined
while
true
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
is
5e-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 than
Number.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.

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.13 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:

  1. If no arguments were passed to this function's invocation, let
    n
    be
    +0
    .
  2. Else, let
    n
    be ?
    ToNumber(value)
    .
  3. In case of
    undefined
    ,
    ToNumber(undefined)
    should return
    NaN
    .

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

💡 Explanation: This happens because

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

💡 Explanation:

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 and
NaN
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

💡 Explanation: If the input string begins with "0", radix is eight (octal) or 10 (decimal). Exactly which radix is chosen is implementation-dependent. ECMAScript 5 specifies that 10 (decimal) is used, but not all browsers support this yet. For this reason always specify a radix when using

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

💡 Explanation:

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
.

Math with
true
and
false

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. If
argument
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

(function() {
  return
  {
    b: 10;
  }
})(); // -> undefined

💡 Explanation:

return
and the returned expression must be in the same line:
(function() {
  return {
    b: 10
  };
})(); // -> { b: 10 }

This is because of a concept called Automatic Semicolon Insertion, which automagically inserts semicolons after most newlines. In the first example, there is a semicolon inserted between the

return
statement and the object literal, so the function returns
undefined
and the object literal is never evaluated.

Chaining assignments on object

var foo = { n: 1 };
var bar = foo;

foo.x = foo = { n: 2 };

foo.x; // -> undefined foo; // -> {n: 2} bar; // -> {n: 1, x: {n: 2}}

From right to left,

{n: 2}
is assigned to foo, and the result of this assignment
{n: 2}
is assigned to foo.x, that's why bar is
{n: 1, x: {n: 2}}
as bar is a reference to foo. But why foo.x is undefined while bar.x is not ?

💡 Explanation:

Foo and bar references the same object

{n: 1}
, and lvalues are resolved before assignations.
foo = {n: 2}
is creating a new object, and so foo is updated to reference that new object. The trick here is foo in
foo.x = ...
as a lvalue was resolved beforehand and still reference the old
foo = {n: 1}
object and update it by adding the x value. After that chain assignments, bar still reference the old foo object, but foo reference the new
{n: 2}
object, where x is not existing.

It's equivalent to:

var foo = { n: 1 };
var bar = foo;

foo = { n: 2 }; // -> {n: 2} bar.x = foo; // -> {n: 1, x: {n: 2}} // bar.x point to the address of the new foo object // it's not equivalent to: bar.x = {n: 2}

Accessing object properties with arrays

var obj = { property: 1 };
var array = ["property"];

obj[array]; // -> 1

What about pseudo-multidimensional arrays?

var map = {};
var x = 1;
var y = 2;
var z = 3;

map[[x, y, z]] = true; map[[x + 10, y, z]] = true;

map["1,2,3"]; // -> true map["11,2,3"]; // -> true

💡 Explanation:

The brackets

[]
operator converts the passed expression using
toString
. Converting a one-element array to a string is akin to converting the contained element to the string:
["property"].toString(); // -> 'property'

Null and Relational Operators

null > 0; // false
null == 0; // false

null >= 0; // true

💡 Explanation:

Long story short, if

null
is less than
0
is
false
, then
null >= 0
is
true
. Read in-depth explanation for this here.

Number.toFixed()
display different numbers

Number.toFixed()
can behave a bit strange in different browsers. Check out this example:
(0.7875).toFixed(3);
// Firefox: -> 0.787
// Chrome: -> 0.787
// IE11: -> 0.788
(0.7876).toFixed(3);
// Firefox: -> 0.788
// Chrome: -> 0.788
// IE11: -> 0.788

💡 Explanation:

While your first instinct may be that IE11 is correct and Firefox/Chrome are wrong, the reality is that Firefox/Chrome are more directly obeying standards for numbers (IEEE-754 Floating Point), while IE11 is minutely disobeying them in (what is probably) an effort to give clearer results.

You can see why this occurs with a few quick tests:

// Confirm the odd result of rounding a 5 down
(0.7875).toFixed(3); // -> 0.787
// It looks like it's just a 5 when you expand to the
// limits of 64-bit (double-precision) float accuracy
(0.7875).toFixed(14); // -> 0.78750000000000
// But what if you go beyond the limit?
(0.7875).toFixed(20); // -> 0.78749999999999997780

Floating point numbers are not stored as a list of decimal digits internally, but through a more complicated methodology that produces tiny inaccuracies that are usually rounded away by toString and similar calls, but are actually present internally.

In this case, that "5" on the end was actually an extremely tiny fraction below a true 5. Rounding it at any reasonable length will render it as a 5... but it is actually not a 5 internally.

IE11, however, will report the value input with only zeros appended to the end even in the toFixed(20) case, as it seems to be forcibly rounding the value to reduce the troubles from hardware limits.

See for reference

NOTE 2
on the ECMA-262 definition for
toFixed
.

Math.max()
less than
Math.min()

Math.min(1, 4, 7, 2); // -> 1
Math.max(1, 4, 7, 2); // -> 7
Math.min(); // -> Infinity
Math.max(); // -> -Infinity
Math.min() > Math.max(); // -> true

💡 Explanation:

Comparing
null
to
0

The following expressions seem to introduce a contradiction:

null == 0; // -> false
null > 0; // -> false
null >= 0; // -> true

How can

null
be neither equal to nor greater than
0
, if
null >= 0
is actually
true
? (This also works with less than in the same way.)

💡 Explanation:

The way these three expressions are evaluated are all different and are responsible for producing this unexpected behavior.

First, the abstract equality comparison

null == 0
. Normally, if this operator can't compare the values on either side properly, it converts both to numbers and compares the numbers. Then, you might expect the following behavior:
// This is not what happens
(null == 0 + null) == +0;
0 == 0;
true;

However, according to a close reading of the spec, the number conversion doesn't actually happen on a side that is

null
or
undefined
. Therefore, if you have
null
on one side of the equal sign, the other side must be
null
or
undefined
for the expression to return
true
. Since this is not the case,
false
is returned.

Next, the relational comparison

null > 0
. The algorithm here, unlike that of the abstract equality operator, will convert
null
to a number. Therefore, we get this behavior:
null > 0
+null = +0
0 > 0
false

Finally, the relational comparison

null >= 0
. You could argue that this expression should be the result of
null > 0 || null == 0
; if this were the case, then the above results would mean that this would also be
false
. However, the
>=
operator in fact works in a very different way, which is basically to take the opposite of the
<
operator. Because our example with the greater than operator above also holds for the less than operator, that means this expression is actually evaluated like so:
null >= 0;
!(null < 0);
!(+null < +0);
!(0 < 0);
!false;
true;

Same variable redeclaration

JS allows to redeclare variables:

a;
a;
// This is also valid
a, a;

Works also in strict mode:

var a, a, a;
var a;
var a;

💡 Explanation:

All definitions are merged into one definition.

Default behavior Array.prototype.sort()

Imagine that you need to sort an array of numbers.

[ 10, 1, 3 ].sort() // -> [ 1, 10, 3 ]

💡 Explanation:

The default sort order is built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.

Hint

Pass

comparefn
if you try to sort anything but string.
[ 10, 1, 3 ].sort((a, b) => a - b) // -> [ 1, 3, 10 ]

resolve() won't return Promise instance

const theObject = {
  "a": 7,
};
const thePromise = new Promise((resolve, reject) => {
  resolve(theObject);
}); // -> Promise instance object

thePromise.then(value => { console.log(value === theObject); // -> true console.log(value); // -> { a: 7 } })

The

value
which is resolved from
thePromise
is exactly
theObject
.

How about input another

Promise
into the
resolve
function?
const theObject = new Promise((resolve, reject) => {
  resolve(7);
}); // -> Promise instance object
const thePromise = new Promise((resolve, reject) => {
  resolve(theObject);
}); // -> Promise instance object

thePromise.then(value => { console.log(value === theObject); // -> false console.log(value); // -> 7 })

💡 Explanation:

This function flattens nested layers of promise-like objects (e.g. a promise that resolves to a promise that resolves to something) into a single layer.

Promise.resolve() on MDN

The specification is ECMAScript 25.6.1.3.2 Promise Resolve Functions. But it is not quite human-friendly.

📚 Other resources

  • wtfjs.com — a collection of those very special irregularities, inconsistencies and just plain painfully unintuitive moments for the language of the web.
  • Wat — A lightning talk by Gary Bernhardt from CodeMash 2012
  • What the... JavaScript? — Kyle Simpsons talk for Forward 2 attempts to “pull out the crazy” from JavaScript. He wants to help you produce cleaner, more elegant, more readable code, then inspire people to contribute to the open source community.

🎓 License

CC 4.0

© Denys Dovhan

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.